Merge "Remove lpy@google.com from services/gpuservice/vts/OWNERS" into main
diff --git a/Android.bp b/Android.bp
index 2520a71..a689b2d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -38,10 +38,17 @@
 
 cc_library_headers {
     name: "native_headers",
+    vendor_available: true,
     host_supported: true,
+    target: {
+        windows: {
+            enabled: true,
+        },
+    },
     export_include_dirs: [
         "include/",
     ],
+    product_available: true,
 }
 
 ndk_headers {
@@ -119,3 +126,9 @@
     srcs: ["aidl/android/hardware/display/IDeviceProductInfoConstants.aidl"],
     path: "aidl",
 }
+
+dirgroup {
+    name: "trusty_dirgroup_frameworks_native",
+    dirs: ["libs/binder"],
+    visibility: ["//trusty/vendor/google/aosp/scripts"],
+}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2ce3fb0..df1ef29 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -24,6 +24,7 @@
                libs/nativewindow/
                libs/renderengine/
                libs/ui/
+               libs/vibrator/
                libs/vr/
                opengl/libs/
                services/bufferhub/
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9c01169..07d16f7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,9 +4,6 @@
       "name": "SurfaceFlinger_test",
       "options": [
         {
-          "include-filter": "*"
-        },
-        {
           // TODO(b/305717998): Deflake and re-enable
           "exclude-filter": "*ChildLayerTest*"
         }
@@ -23,12 +20,7 @@
   ],
   "hwasan-postsubmit": [
     {
-      "name": "SurfaceFlinger_test",
-      "options": [
-        {
-          "include-filter": "*"
-        }
-      ]
+      "name": "SurfaceFlinger_test"
     }
   ]
 }
diff --git a/aidl/binder/android/os/PersistableBundle.aidl b/aidl/binder/android/os/PersistableBundle.aidl
index 248e973..9b11109 100644
--- a/aidl/binder/android/os/PersistableBundle.aidl
+++ b/aidl/binder/android/os/PersistableBundle.aidl
@@ -17,4 +17,4 @@
 
 package android.os;
 
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h" ndk_header "android/persistable_bundle_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h" ndk_header "android/persistable_bundle_aidl.h" rust_type "binder::PersistableBundle";
diff --git a/cmds/atrace/atrace_userdebug.rc b/cmds/atrace/atrace_userdebug.rc
index fa7be18..041ffe1 100644
--- a/cmds/atrace/atrace_userdebug.rc
+++ b/cmds/atrace/atrace_userdebug.rc
@@ -24,3 +24,7 @@
     chmod 0666 /sys/kernel/debug/tracing/events/raw_syscalls/sys_enter/filter
     chmod 0666 /sys/kernel/tracing/events/raw_syscalls/sys_exit/filter
     chmod 0666 /sys/kernel/debug/tracing/events/raw_syscalls/sys_exit/filter
+
+    # Allow traced_probes to use the kprobe interface
+    chmod 0666 /sys/kernel/debug/tracing/kprobe_events
+    chmod 0666 /sys/kernel/tracing/kprobe_events
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index b22cc2a..1397ae6 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -83,14 +83,16 @@
         "aconfig_lib_cc_static_link.defaults",
         "dumpstate_cflag_defaults",
     ],
+    // See README.md: "Dumpstate philosophy: exec not link"
+    // Do not add things here - keep dumpstate as simple as possible and exec where possible.
     shared_libs: [
         "android.hardware.dumpstate@1.0",
         "android.hardware.dumpstate@1.1",
         "android.hardware.dumpstate-V1-ndk",
         "libziparchive",
         "libbase",
-        "libbinder",
-        "libbinder_ndk",
+        "libbinder", // BAD: dumpstate should not link code directly, should only exec binaries
+        "libbinder_ndk", // BAD: dumpstate should not link code directly, should only exec binaries
         "libcrypto",
         "libcutils",
         "libdebuggerd_client",
@@ -98,11 +100,11 @@
         "libdumpstateutil",
         "libdumputils",
         "libhardware_legacy",
-        "libhidlbase",
+        "libhidlbase", // BAD: dumpstate should not link code directly, should only exec binaries
         "liblog",
         "libutils",
-        "libvintf",
-        "libbinderdebug",
+        "libvintf", // BAD: dumpstate should not link code directly, should only exec binaries
+        "libbinderdebug", // BAD: dumpstate should not link code directly, should only exec binaries
         "packagemanager_aidl-cpp",
         "server_configurable_flags",
         "device_policy_aconfig_flags_c_lib",
@@ -128,6 +130,7 @@
         "main.cpp",
     ],
     required: [
+        "alloctop",
         "atrace",
         "bugreport_procdump",
         "dmabuf_dump",
diff --git a/cmds/dumpstate/DumpstateInternal.cpp b/cmds/dumpstate/DumpstateInternal.cpp
index 6f7fea3..ce7c55c 100644
--- a/cmds/dumpstate/DumpstateInternal.cpp
+++ b/cmds/dumpstate/DumpstateInternal.cpp
@@ -108,7 +108,7 @@
 
     const uint32_t cap_syslog_mask = CAP_TO_MASK(CAP_SYSLOG);
     const uint32_t cap_syslog_index = CAP_TO_INDEX(CAP_SYSLOG);
-    bool has_cap_syslog = (capdata[cap_syslog_index].effective & cap_syslog_mask) != 0;
+    bool has_cap_syslog = (capdata[cap_syslog_index].permitted & cap_syslog_mask) != 0;
 
     memset(&capdata, 0, sizeof(capdata));
     if (has_cap_syslog) {
diff --git a/cmds/dumpstate/DumpstateService.cpp b/cmds/dumpstate/DumpstateService.cpp
index ba0a38a..a8d12a1 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -39,6 +39,7 @@
     std::string calling_package;
     int32_t user_id = -1;
     bool keep_bugreport_on_retrieval = false;
+    bool skip_user_consent = false;
 };
 
 static binder::Status exception(uint32_t code, const std::string& msg,
@@ -62,7 +63,8 @@
 
 [[noreturn]] static void* dumpstate_thread_retrieve(void* data) {
     std::unique_ptr<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
-    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, ds_info->keep_bugreport_on_retrieval);
+    ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package,
+    ds_info->keep_bugreport_on_retrieval, ds_info->skip_user_consent);
     MYLOGD("Finished retrieving a bugreport. Exiting.\n");
     exit(0);
 }
@@ -116,7 +118,8 @@
                                                 int bugreport_mode,
                                                 int bugreport_flags,
                                                 const sp<IDumpstateListener>& listener,
-                                                bool is_screenshot_requested) {
+                                                bool is_screenshot_requested,
+                                                bool skip_user_consent) {
     MYLOGI("startBugreport() with mode: %d\n", bugreport_mode);
 
     // Ensure there is only one bugreport in progress at a time.
@@ -151,7 +154,7 @@
 
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
     options->Initialize(static_cast<Dumpstate::BugreportMode>(bugreport_mode), bugreport_flags,
-                        bugreport_fd, screenshot_fd, is_screenshot_requested);
+                        bugreport_fd, screenshot_fd, is_screenshot_requested, skip_user_consent);
 
     if (bugreport_fd.get() == -1 || (options->do_screenshot && screenshot_fd.get() == -1)) {
         MYLOGE("Invalid filedescriptor");
@@ -207,6 +210,7 @@
     android::base::unique_fd bugreport_fd,
     const std::string& bugreport_file,
     const bool keep_bugreport_on_retrieval,
+    const bool skip_user_consent,
     const sp<IDumpstateListener>& listener) {
 
     ds_ = &(Dumpstate::GetInstance());
@@ -216,6 +220,7 @@
     ds_info->calling_package = calling_package;
     ds_info->user_id = user_id;
     ds_info->keep_bugreport_on_retrieval = keep_bugreport_on_retrieval;
+    ds_info->skip_user_consent = skip_user_consent;
     ds_->listener_ = listener;
     std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
     // Use a /dev/null FD when initializing options since none is provided.
@@ -223,7 +228,7 @@
         TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
 
     options->Initialize(Dumpstate::BugreportMode::BUGREPORT_DEFAULT,
-                        0, bugreport_fd, devnull_fd, false);
+                        0, bugreport_fd, devnull_fd, false, skip_user_consent);
 
     if (bugreport_fd.get() == -1) {
         MYLOGE("Invalid filedescriptor");
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 7b76c36..c99f70e 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -44,7 +44,7 @@
                                   android::base::unique_fd bugreport_fd,
                                   android::base::unique_fd screenshot_fd, int bugreport_mode,
                                   int bugreport_flags, const sp<IDumpstateListener>& listener,
-                                  bool is_screenshot_requested) override;
+                                  bool is_screenshot_requested, bool skip_user_consent) override;
 
     binder::Status retrieveBugreport(int32_t calling_uid,
                                      const std::string& calling_package,
@@ -52,6 +52,7 @@
                                      android::base::unique_fd bugreport_fd,
                                      const std::string& bugreport_file,
                                      const bool keep_bugreport_on_retrieval,
+                                     const bool skip_user_consent,
                                      const sp<IDumpstateListener>& listener)
                                      override;
 
diff --git a/cmds/dumpstate/README.md b/cmds/dumpstate/README.md
index 26dabbb..3ab971a 100644
--- a/cmds/dumpstate/README.md
+++ b/cmds/dumpstate/README.md
@@ -21,6 +21,18 @@
 mmm -j frameworks/native/cmds/dumpstate device/acme/secret_device/dumpstate/ hardware/interfaces/dumpstate
 ```
 
+## Dumpstate philosophy: exec not link
+
+Never link code directly into dumpstate. Dumpstate should execute many
+binaries and collect the results. In general, code should fail hard fail fast,
+but dumpstate is the last to solve many Android bugs. Oftentimes, failures
+in core Android infrastructure or tools are issues that cause problems in
+bugreport directly, so bugreport should not rely on these tools working.
+We want dumpstate to have as minimal of code loaded in process so that
+only that core subset needs to be bugfree for bugreport to work. Even if
+many pieces of Android break, that should not prevent dumpstate from
+working.
+
 ## To build, deploy, and take a bugreport
 
 ```
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 97c470e..3b8fde9 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -96,7 +96,8 @@
     void startBugreport(int callingUid, @utf8InCpp String callingPackage,
                         FileDescriptor bugreportFd, FileDescriptor screenshotFd,
                         int bugreportMode, int bugreportFlags,
-                        IDumpstateListener listener, boolean isScreenshotRequested);
+                        IDumpstateListener listener, boolean isScreenshotRequested,
+                        boolean skipUserConsent);
 
     /**
      * Cancels the bugreport currently in progress.
@@ -130,5 +131,6 @@
                            FileDescriptor bugreportFd,
                            @utf8InCpp String bugreportFile,
                            boolean keepBugreportOnRetrieval,
+                           boolean skipUserConsent,
                            IDumpstateListener listener);
 }
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 1e5dd97..ab0274a 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -128,6 +128,9 @@
 using android::os::dumpstate::TaskQueue;
 using android::os::dumpstate::WaitForTask;
 
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// Do not add more complicated variables here, prefer to execute only. Don't link more code here.
+
 // Keep in sync with
 // frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
 static const int TRACE_DUMP_TIMEOUT_MS = 10000; // 10 seconds
@@ -170,6 +173,7 @@
 #define ALT_PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops-0"
 #define BLK_DEV_SYS_DIR "/sys/block"
 
+#define AFLAGS "/system/bin/aflags"
 #define RECOVERY_DIR "/cache/recovery"
 #define RECOVERY_DATA_DIR "/data/misc/recovery"
 #define UPDATE_ENGINE_LOG_DIR "/data/misc/update_engine_log"
@@ -190,7 +194,6 @@
 #define CGROUPFS_DIR "/sys/fs/cgroup"
 #define SDK_EXT_INFO "/apex/com.android.sdkext/bin/derive_sdk"
 #define DROPBOX_DIR "/data/system/dropbox"
-#define PRINT_FLAGS "/system/bin/printflags"
 #define UWB_LOG_DIR "/data/misc/apexdata/com.android.uwb/log"
 
 // TODO(narayan): Since this information has to be kept in sync
@@ -1256,6 +1259,17 @@
     RunCommand("IP RULES v6", {"ip", "-6", "rule", "show"});
 }
 
+static void DumpKernelMemoryAllocations() {
+    if (!access("/proc/allocinfo", F_OK)) {
+        // Print the top 100 biggest memory allocations of at least one byte.
+        // The output is sorted by size, descending.
+        RunCommand("KERNEL MEMORY ALLOCATIONS",
+                   {"alloctop", "--once", "--sort", "s", "--min", "1", "--lines", "100"});
+    }
+}
+
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// This should all be moved into a separate binary rather than have complex logic here.
 static Dumpstate::RunStatus RunDumpsysTextByPriority(const std::string& title, int priority,
                                                      std::chrono::milliseconds timeout,
                                                      std::chrono::milliseconds service_timeout) {
@@ -1336,6 +1350,8 @@
                                     service_timeout);
 }
 
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// This should all be moved into a separate binary rather than have complex logic here.
 static Dumpstate::RunStatus RunDumpsysProto(const std::string& title, int priority,
                                             std::chrono::milliseconds timeout,
                                             std::chrono::milliseconds service_timeout) {
@@ -1417,6 +1433,8 @@
  * Dumpstate can pick up later and output to the bugreport. Using STDOUT_FILENO
  * if it's not running in the parallel task.
  */
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// This should all be moved into a separate binary rather than have complex logic here.
 static void DumpHals(int out_fd = STDOUT_FILENO) {
     RunCommand("HARDWARE HALS", {"lshal", "--all", "--types=all"},
                CommandOptions::WithTimeout(10).AsRootIfAvailable().Build(),
@@ -1473,6 +1491,9 @@
     }
 }
 
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// This should all be moved into a separate binary rather than have complex logic here.
+//
 // Dump all of the files that make up the vendor interface.
 // See the files listed in dumpFileList() for the latest list of files.
 static void DumpVintf() {
@@ -1502,6 +1523,8 @@
     printf("------ EXTERNAL FRAGMENTATION INFO ------\n");
     std::ifstream ifs("/proc/buddyinfo");
     auto unusable_index_regex = std::regex{"Node\\s+([0-9]+),\\s+zone\\s+(\\S+)\\s+(.*)"};
+    // BAD - See README.md: "Dumpstate philosophy: exec not link"
+    // This should all be moved into a separate binary rather than have complex logic here.
     for (std::string line; std::getline(ifs, line);) {
         std::smatch match_results;
         if (std::regex_match(line, match_results, unusable_index_regex)) {
@@ -1559,6 +1582,13 @@
                CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10));
 
     printf("========================================================\n");
+    printf("== Networking Policy\n");
+    printf("========================================================\n");
+
+    RunDumpsys("DUMPSYS NETWORK POLICY", {"netpolicy"}, CommandOptions::WithTimeout(90).Build(),
+               SEC_TO_MSEC(10));
+
+    printf("========================================================\n");
     printf("== Dropbox crashes\n");
     printf("========================================================\n");
 
@@ -1570,6 +1600,7 @@
     printf("== ANR Traces\n");
     printf("========================================================\n");
 
+    ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX);
     AddAnrTraceFiles();
 
     printf("========================================================\n");
@@ -1757,6 +1788,8 @@
 
     DoKmsg();
 
+    DumpKernelMemoryAllocations();
+
     DumpShutdownCheckpoints();
 
     DumpIpAddrAndRules();
@@ -1782,8 +1815,8 @@
     DumpFile("PRODUCT BUILD-TIME RELEASE FLAGS", "/product/etc/build_flags.json");
     DumpFile("VENDOR BUILD-TIME RELEASE FLAGS", "/vendor/etc/build_flags.json");
 
-    RunCommand("ACONFIG FLAGS", {PRINT_FLAGS},
-               CommandOptions::WithTimeout(10).Always().DropRoot().Build());
+    RunCommand("ACONFIG FLAGS DUMP", {AFLAGS, "list"},
+               CommandOptions::WithTimeout(10).Always().AsRootIfAvailable().Build());
 
     RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
 
@@ -2428,6 +2461,8 @@
     return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::DEFAULT;
 }
 
+// BAD - See README.md: "Dumpstate philosophy: exec not link"
+// This should all be moved into a separate binary rather than have complex logic here.
 static void DoDumpstateBoardHidl(
     const sp<dumpstate_hal_hidl_1_0::IDumpstateDevice> dumpstate_hal_1_0,
     const std::vector<::ndk::ScopedFileDescriptor>& dumpstate_fds,
@@ -2979,9 +3014,11 @@
                                         int bugreport_flags,
                                         const android::base::unique_fd& bugreport_fd_in,
                                         const android::base::unique_fd& screenshot_fd_in,
-                                        bool is_screenshot_requested) {
+                                        bool is_screenshot_requested,
+                                        bool skip_user_consent) {
     this->use_predumped_ui_data = bugreport_flags & BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA;
     this->is_consent_deferred = bugreport_flags & BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
+    this->skip_user_consent = skip_user_consent;
     // Duplicate the fds because the passed in fds don't outlive the binder transaction.
     bugreport_fd.reset(fcntl(bugreport_fd_in.get(), F_DUPFD_CLOEXEC, 0));
     screenshot_fd.reset(fcntl(screenshot_fd_in.get(), F_DUPFD_CLOEXEC, 0));
@@ -3069,46 +3106,52 @@
 }
 
 Dumpstate::RunStatus Dumpstate::Retrieve(int32_t calling_uid, const std::string& calling_package,
-                                         const bool keep_bugreport_on_retrieval) {
+                                         const bool keep_bugreport_on_retrieval,
+                                         const bool skip_user_consent) {
     Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package,
-                                                    keep_bugreport_on_retrieval);
+                                                    keep_bugreport_on_retrieval,
+                                                    skip_user_consent);
     HandleRunStatus(status);
     return status;
 }
 
 Dumpstate::RunStatus  Dumpstate::RetrieveInternal(int32_t calling_uid,
                                                   const std::string& calling_package,
-                                                  const bool keep_bugreport_on_retrieval) {
-  consent_callback_ = new ConsentCallback();
-  const String16 incidentcompanion("incidentcompanion");
-  sp<android::IBinder> ics(
-      defaultServiceManager()->checkService(incidentcompanion));
-  android::String16 package(calling_package.c_str());
-  if (ics != nullptr) {
-    MYLOGD("Checking user consent via incidentcompanion service\n");
-    android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
-        calling_uid, package, String16(), String16(),
-        0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
-  } else {
-    MYLOGD(
-        "Unable to check user consent; incidentcompanion service unavailable\n");
-    return RunStatus::USER_CONSENT_TIMED_OUT;
-  }
-  UserConsentResult consent_result = consent_callback_->getResult();
-  int timeout_ms = 30 * 1000;
-  while (consent_result == UserConsentResult::UNAVAILABLE &&
-      consent_callback_->getElapsedTimeMs() < timeout_ms) {
-    sleep(1);
-    consent_result = consent_callback_->getResult();
-  }
-  if (consent_result == UserConsentResult::DENIED) {
-    return RunStatus::USER_CONSENT_DENIED;
-  }
-  if (consent_result == UserConsentResult::UNAVAILABLE) {
-    MYLOGD("Canceling user consent request via incidentcompanion service\n");
-    android::interface_cast<android::os::IIncidentCompanion>(ics)->cancelAuthorization(
-        consent_callback_.get());
-    return RunStatus::USER_CONSENT_TIMED_OUT;
+                                                  const bool keep_bugreport_on_retrieval,
+                                                  const bool skip_user_consent) {
+  if (!android::app::admin::flags::onboarding_consentless_bugreports() || !skip_user_consent) {
+      consent_callback_ = new ConsentCallback();
+      const String16 incidentcompanion("incidentcompanion");
+      sp<android::IBinder> ics(
+          defaultServiceManager()->checkService(incidentcompanion));
+      android::String16 package(calling_package.c_str());
+      if (ics != nullptr) {
+        MYLOGD("Checking user consent via incidentcompanion service\n");
+
+        android::interface_cast<android::os::IIncidentCompanion>(ics)->authorizeReport(
+            calling_uid, package, String16(), String16(),
+            0x1 /* FLAG_CONFIRMATION_DIALOG */, consent_callback_.get());
+      } else {
+        MYLOGD(
+            "Unable to check user consent; incidentcompanion service unavailable\n");
+        return RunStatus::USER_CONSENT_TIMED_OUT;
+      }
+      UserConsentResult consent_result = consent_callback_->getResult();
+      int timeout_ms = 30 * 1000;
+      while (consent_result == UserConsentResult::UNAVAILABLE &&
+          consent_callback_->getElapsedTimeMs() < timeout_ms) {
+        sleep(1);
+        consent_result = consent_callback_->getResult();
+      }
+      if (consent_result == UserConsentResult::DENIED) {
+        return RunStatus::USER_CONSENT_DENIED;
+      }
+      if (consent_result == UserConsentResult::UNAVAILABLE) {
+        MYLOGD("Canceling user consent request via incidentcompanion service\n");
+        android::interface_cast<android::os::IIncidentCompanion>(ics)->cancelAuthorization(
+            consent_callback_.get());
+        return RunStatus::USER_CONSENT_TIMED_OUT;
+      }
   }
 
   bool copy_succeeded =
@@ -3357,6 +3400,12 @@
     // duration is logged into MYLOG instead.
     PrintHeader();
 
+    bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
+    if (options_->use_predumped_ui_data && !system_trace_exists) {
+        MYLOGW("Ignoring 'use predumped data' flag because no predumped data is available");
+        options_->use_predumped_ui_data = false;
+    }
+
     std::future<std::string> snapshot_system_trace;
 
     bool is_dumpstate_restricted =
@@ -3581,7 +3630,9 @@
 
 void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
     if (multiuser_get_app_id(calling_uid) == AID_SHELL ||
-        !CalledByApi() || options_->is_consent_deferred) {
+        !CalledByApi() || options_->is_consent_deferred ||
+        (android::app::admin::flags::onboarding_consentless_bugreports() &&
+        options_->skip_user_consent)) {
         // No need to get consent for shell triggered dumpstates, or not
         // through bugreporting API (i.e. no fd to copy back), or when consent
         // is deferred.
@@ -3667,7 +3718,8 @@
     // If the caller has asked to copy the bugreport over to their directory, we need explicit
     // user consent (unless the caller is Shell).
     UserConsentResult consent_result;
-    if (multiuser_get_app_id(calling_uid) == AID_SHELL) {
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL || (options_->skip_user_consent
+    && android::app::admin::flags::onboarding_consentless_bugreports())) {
         consent_result = UserConsentResult::APPROVED;
     } else {
         consent_result = consent_callback_->getResult();
@@ -4608,7 +4660,7 @@
 void Dumpstate::TakeScreenshot(const std::string& path) {
     const std::string& real_path = path.empty() ? screenshot_path_ : path;
     int status =
-        RunCommand("", {"/system/bin/screencap", "-p", real_path},
+        RunCommand("", {"screencap", "-p", real_path},
                    CommandOptions::WithTimeout(10).Always().DropRoot().RedirectStderr().Build());
     if (status == 0) {
         MYLOGD("Screenshot saved on %s\n", real_path.c_str());
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 46d949e..fcb8cf3 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -364,7 +364,7 @@
      * Initialize() dumpstate before calling this method.
      */
     RunStatus Retrieve(int32_t calling_uid, const std::string& calling_package,
-                        const bool keep_bugreport_on_retrieval);
+                        const bool keep_bugreport_on_retrieval, const bool skip_user_consent);
 
 
 
@@ -412,6 +412,7 @@
         bool do_screenshot = false;
         bool is_screenshot_copied = false;
         bool is_consent_deferred = false;
+        bool skip_user_consent = false;
         bool is_remote_mode = false;
         bool show_header_only = false;
         bool telephony_only = false;
@@ -448,7 +449,8 @@
         void Initialize(BugreportMode bugreport_mode, int bugreport_flags,
                         const android::base::unique_fd& bugreport_fd,
                         const android::base::unique_fd& screenshot_fd,
-                        bool is_screenshot_requested);
+                        bool is_screenshot_requested,
+                        bool skip_user_consent);
 
         /* Returns true if the options set so far are consistent. */
         bool ValidateOptions() const;
@@ -564,7 +566,8 @@
   private:
     RunStatus RunInternal(int32_t calling_uid, const std::string& calling_package);
     RunStatus RetrieveInternal(int32_t calling_uid, const std::string& calling_package,
-                                const bool keep_bugreport_on_retrieval);
+                                const bool keep_bugreport_on_retrieval,
+                                const bool skip_user_consent);
 
     RunStatus DumpstateDefaultAfterCritical();
     RunStatus dumpstate();
diff --git a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
index ccf64fe..a29923a 100644
--- a/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_smoke_test.cpp
@@ -507,7 +507,7 @@
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener,
-                                  true);
+                                  true, false);
     // startBugreport is an async call. Verify binder call succeeded first, then wait till listener
     // gets expected callbacks.
     EXPECT_TRUE(status.isOk());
@@ -545,7 +545,7 @@
     android::binder::Status status =
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd), 2000,  // invalid bugreport mode
-                                  flags, listener, false);
+                                  flags, listener, false, false);
     EXPECT_EQ(listener->getErrorCode(), IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
 
     // The service should have died, freeing itself up for a new invocation.
@@ -579,7 +579,7 @@
         ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd),
                                   std::move(screenshot_fd),
                                   Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags, listener1,
-                                  true);
+                                  true, false);
     EXPECT_TRUE(status.isOk());
 
     // try to make another call to startBugreport. This should fail.
@@ -587,7 +587,7 @@
     status = ds_binder->startBugreport(123, "com.example.package", std::move(bugreport_fd2),
                                         std::move(screenshot_fd2),
                                        Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, flags,
-                                       listener2, true);
+                                       listener2, true, false);
     EXPECT_FALSE(status.isOk());
     WaitTillExecutionComplete(listener2.get());
     EXPECT_EQ(listener2->getErrorCode(),
diff --git a/cmds/dumpstate/tests/dumpstate_test.cpp b/cmds/dumpstate/tests/dumpstate_test.cpp
index 2afabed..18c2f94 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -239,7 +239,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeFullBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_screenshot);
 
     // Other options retain default values
@@ -253,7 +253,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeInteractiveBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_INTERACTIVE, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_progress_updates);
     EXPECT_TRUE(options_.do_screenshot);
 
@@ -267,7 +267,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeRemoteBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_REMOTE, 0, fd, fd, false, false);
     EXPECT_TRUE(options_.is_remote_mode);
     EXPECT_FALSE(options_.do_vibrate);
     EXPECT_FALSE(options_.do_screenshot);
@@ -281,7 +281,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWearBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WEAR, 0, fd, fd, true, false);
     EXPECT_TRUE(options_.do_screenshot);
     EXPECT_TRUE(options_.do_progress_updates);
 
@@ -296,7 +296,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeTelephonyBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_TELEPHONY, 0, fd, fd, false, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.telephony_only);
     EXPECT_TRUE(options_.do_progress_updates);
@@ -311,7 +311,7 @@
 }
 
 TEST_F(DumpOptionsTest, InitializeWifiBugReport) {
-    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false);
+    options_.Initialize(Dumpstate::BugreportMode::BUGREPORT_WIFI, 0, fd, fd, false, false);
     EXPECT_FALSE(options_.do_screenshot);
     EXPECT_TRUE(options_.wifi_only);
 
@@ -491,12 +491,12 @@
     int flags = Dumpstate::BugreportFlag::BUGREPORT_USE_PREDUMPED_UI_DATA |
                 Dumpstate::BugreportFlag::BUGREPORT_FLAG_DEFER_CONSENT;
     options_.Initialize(
-      Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true);
+      Dumpstate::BugreportMode::BUGREPORT_FULL, flags, fd, fd, true, false);
     EXPECT_TRUE(options_.is_consent_deferred);
     EXPECT_TRUE(options_.use_predumped_ui_data);
 
     options_.Initialize(
-      Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true);
+      Dumpstate::BugreportMode::BUGREPORT_FULL, 0, fd, fd, true, false);
     EXPECT_FALSE(options_.is_consent_deferred);
     EXPECT_FALSE(options_.use_predumped_ui_data);
 }
diff --git a/cmds/dumpsys/tests/dumpsys_test.cpp b/cmds/dumpsys/tests/dumpsys_test.cpp
index b8e5ce1..17ae0aa 100644
--- a/cmds/dumpsys/tests/dumpsys_test.cpp
+++ b/cmds/dumpsys/tests/dumpsys_test.cpp
@@ -67,6 +67,8 @@
     MOCK_METHOD2(unregisterForNotifications, status_t(const String16&,
                                              const sp<LocalRegistrationCallback>&));
     MOCK_METHOD0(getServiceDebugInfo, std::vector<ServiceDebugInfo>());
+    MOCK_METHOD1(enableAddServiceCache, void(bool));
+
   protected:
     MOCK_METHOD0(onAsBinder, IBinder*());
 };
diff --git a/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
index db3fd77..e91e5da 100644
--- a/cmds/evemu-record/main.rs
+++ b/cmds/evemu-record/main.rs
@@ -50,8 +50,10 @@
     /// The first event received from the device.
     FirstEvent,
 
-    /// The time when the system booted.
-    Boot,
+    /// The Unix epoch (00:00:00 UTC on 1st January 1970), so that all timestamps are Unix
+    /// timestamps. This makes the events in the recording easier to match up with those from other
+    /// log sources.
+    Epoch,
 }
 
 fn get_choice(max: u32) -> u32 {
@@ -188,7 +190,7 @@
         //
         // [0]: https://gitlab.freedesktop.org/libevdev/evemu/-/commit/eba96a4d2be7260b5843e65c4b99c8b06a1f4c9d
         TimestampBase::FirstEvent => event.time - TimeVal::new(0, 1),
-        TimestampBase::Boot => TimeVal::new(0, 0),
+        TimestampBase::Epoch => TimeVal::new(0, 0),
     };
     print_event(output, &event.offset_time_by(start_time))?;
     loop {
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index c163095..77e7328 100644
--- a/cmds/flatland/GLHelper.cpp
+++ b/cmds/flatland/GLHelper.cpp
@@ -18,6 +18,7 @@
 
 #include <GLES2/gl2.h>
 #include <GLES2/gl2ext.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/SurfaceComposerClient.h>
 #include <ui/DisplayMode.h>
 
@@ -202,6 +203,14 @@
 
 bool GLHelper::createNamedSurfaceTexture(GLuint name, uint32_t w, uint32_t h,
         sp<GLConsumer>* glConsumer, EGLSurface* surface) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    sp<GLConsumer> glc = new GLConsumer(name, GL_TEXTURE_EXTERNAL_OES, false, true);
+    glc->setDefaultBufferSize(w, h);
+    glc->getSurface()->setMaxDequeuedBufferCount(2);
+    glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
+
+    sp<ANativeWindow> anw = glc->getSurface();
+#else
     sp<IGraphicBufferProducer> producer;
     sp<IGraphicBufferConsumer> consumer;
     BufferQueue::createBufferQueue(&producer, &consumer);
@@ -212,6 +221,7 @@
     glc->setConsumerUsageBits(GRALLOC_USAGE_HW_COMPOSER);
 
     sp<ANativeWindow> anw = new Surface(producer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     EGLSurface s = eglCreateWindowSurface(mDisplay, mConfig, anw.get(), nullptr);
     if (s == EGL_NO_SURFACE) {
         fprintf(stderr, "eglCreateWindowSurface error: %#x\n", eglGetError());
diff --git a/cmds/idlcli/Android.bp b/cmds/idlcli/Android.bp
index c18d3f5..50c2cd8 100644
--- a/cmds/idlcli/Android.bp
+++ b/cmds/idlcli/Android.bp
@@ -24,7 +24,7 @@
 cc_defaults {
     name: "idlcli-defaults",
     shared_libs: [
-        "android.hardware.vibrator-V2-ndk",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/cmds/idlcli/vibrator.h b/cmds/idlcli/vibrator.h
index e100eac..b943495 100644
--- a/cmds/idlcli/vibrator.h
+++ b/cmds/idlcli/vibrator.h
@@ -49,7 +49,7 @@
 template <typename I>
 inline auto getService(std::string name) {
     const auto instance = std::string() + I::descriptor + "/" + name;
-    auto vibBinder = ndk::SpAIBinder(AServiceManager_getService(instance.c_str()));
+    auto vibBinder = ndk::SpAIBinder(AServiceManager_checkService(instance.c_str()));
     return I::fromBinder(vibBinder);
 }
 
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index 4486bd6..db56551 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -119,7 +119,6 @@
  */
 static constexpr const char* kAppDataIsolationEnabledProperty = "persist.zygote.app_data_isolation";
 static constexpr const char* kMntSdcardfs = "/mnt/runtime/default/";
-static constexpr const char* kMntFuse = "/mnt/pass_through/0/";
 
 static std::atomic<bool> sAppDataIsolationEnabled(false);
 
@@ -3697,7 +3696,9 @@
         std::getline(in, ignored);
 
         if (android::base::GetBoolProperty(kFuseProp, false)) {
-            if (target.find(kMntFuse) == 0) {
+            const std::regex kMntFuseRe =
+                    std::regex(R"(^/mnt/pass_through/(0|[0-9]+/[A-Z0-9]{4}-[A-Z0-9]{4}).*)");
+            if (std::regex_match(target, kMntFuseRe)) {
                 LOG(DEBUG) << "Found storage mount " << source << " at " << target;
                 mStorageMounts[source] = target;
             }
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index c86adef..c818e0d 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -16,6 +16,7 @@
 
 #include <fcntl.h>
 #include <linux/unistd.h>
+#include <sched.h>
 #include <sys/mount.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index 28bd793..5690e2f 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -23,6 +23,9 @@
 TARGET_SLOT="$1"
 STATUS_FD="$2"
 
+# "1" if the script is triggered by the `UpdateEngine.triggerPostinstall` API. Empty otherwise.
+TRIGGERED_BY_API="$3"
+
 # Maximum number of packages/steps.
 MAXIMUM_PACKAGES=1000
 
@@ -50,6 +53,55 @@
   exit 1
 fi
 
+# A source that infinitely emits arbitrary lines.
+# When connected to STDIN of another process, this source keeps STDIN open until
+# the consumer process closes STDIN or this script dies.
+# In practice, the pm command keeps consuming STDIN, so we don't need to worry
+# about running out of buffer space.
+function infinite_source {
+  while echo .; do
+    sleep 1
+  done
+}
+
+if [[ "$TRIGGERED_BY_API" = "1" ]]; then
+  # During OTA installation, the script is called the first time, and
+  # `TRIGGERED_BY_API` can never be "1". `TRIGGERED_BY_API` being "1" means this
+  # is the second call to this script, through the
+  # `UpdateEngine.triggerPostinstall` API.
+  # When we reach here, it means Pre-reboot Dexopt is enabled in asynchronous
+  # mode and the job scheduler determined that it's the time to run the job.
+  # Start Pre-reboot Dexopt now and wait for it to finish.
+  infinite_source | pm art on-ota-staged --start
+  exit $?
+fi
+
+PR_DEXOPT_JOB_VERSION="$(pm art pr-dexopt-job --version)"
+if (( $? == 0 )) && (( $PR_DEXOPT_JOB_VERSION >= 3 )); then
+  # Delegate to Pre-reboot Dexopt, a feature of ART Service.
+  # ART Service decides what to do with this request:
+  # - If Pre-reboot Dexopt is disabled or unsupported, the command returns
+  #   non-zero.
+  #   This is always the case if the current system is Android 14 or earlier.
+  # - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks
+  #   until Pre-reboot Dexopt finishes, and returns zero no matter it succeeds
+  #   or not.
+  #   This is the default behavior if the current system is Android 15.
+  # - If Pre-reboot Dexopt is enabled in asynchronous mode, the command
+  #   schedules an asynchronous job and returns 0 immediately.
+  #   Later, when the device is idle and charging, the job will be run by the
+  #   job scheduler. It will call this script again through the
+  #   `UpdateEngine.triggerPostinstall` API, with `TRIGGERED_BY_API` being "1".
+  #   This is always the case if the current system is Android 16 or later.
+  if infinite_source | pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
+    # Handled by Pre-reboot Dexopt.
+    exit 0
+  fi
+  echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt."
+else
+  echo "Pre-reboot Dexopt is too old. Fall back to otapreopt."
+fi
+
 if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then
   # We require an updated chroot wrapper that reads dexopt commands from stdin.
   # Even if we kept compat with the old binary, the OTA preopt wouldn't work due
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-0 b/cmds/installd/tests/corpus/seed-2024-08-29-0
new file mode 100644
index 0000000..a09fc84
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-0
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-1 b/cmds/installd/tests/corpus/seed-2024-08-29-1
new file mode 100644
index 0000000..c96616a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-1
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-10 b/cmds/installd/tests/corpus/seed-2024-08-29-10
new file mode 100644
index 0000000..0b21bd1
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-10
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-100 b/cmds/installd/tests/corpus/seed-2024-08-29-100
new file mode 100644
index 0000000..225d123
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-100
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-101 b/cmds/installd/tests/corpus/seed-2024-08-29-101
new file mode 100644
index 0000000..c507b57
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-101
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-102 b/cmds/installd/tests/corpus/seed-2024-08-29-102
new file mode 100644
index 0000000..e75ef89
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-102
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-103 b/cmds/installd/tests/corpus/seed-2024-08-29-103
new file mode 100644
index 0000000..fb28f4d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-103
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-104 b/cmds/installd/tests/corpus/seed-2024-08-29-104
new file mode 100644
index 0000000..b5a2222
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-104
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-105 b/cmds/installd/tests/corpus/seed-2024-08-29-105
new file mode 100644
index 0000000..a126c0e
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-105
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-106 b/cmds/installd/tests/corpus/seed-2024-08-29-106
new file mode 100644
index 0000000..ad84e57
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-106
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-107 b/cmds/installd/tests/corpus/seed-2024-08-29-107
new file mode 100644
index 0000000..6a2bc6f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-107
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-108 b/cmds/installd/tests/corpus/seed-2024-08-29-108
new file mode 100644
index 0000000..578b55a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-108
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-109 b/cmds/installd/tests/corpus/seed-2024-08-29-109
new file mode 100644
index 0000000..44f853d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-109
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-11 b/cmds/installd/tests/corpus/seed-2024-08-29-11
new file mode 100644
index 0000000..28fd841
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-11
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-110 b/cmds/installd/tests/corpus/seed-2024-08-29-110
new file mode 100644
index 0000000..a013ee8
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-110
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-111 b/cmds/installd/tests/corpus/seed-2024-08-29-111
new file mode 100644
index 0000000..1bb6185
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-111
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-112 b/cmds/installd/tests/corpus/seed-2024-08-29-112
new file mode 100644
index 0000000..83008e9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-112
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-113 b/cmds/installd/tests/corpus/seed-2024-08-29-113
new file mode 100644
index 0000000..c9460cb
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-113
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-114 b/cmds/installd/tests/corpus/seed-2024-08-29-114
new file mode 100644
index 0000000..feb0384
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-114
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-115 b/cmds/installd/tests/corpus/seed-2024-08-29-115
new file mode 100644
index 0000000..cd28076
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-115
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-116 b/cmds/installd/tests/corpus/seed-2024-08-29-116
new file mode 100644
index 0000000..c48730e
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-116
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-117 b/cmds/installd/tests/corpus/seed-2024-08-29-117
new file mode 100644
index 0000000..bde1be0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-117
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-118 b/cmds/installd/tests/corpus/seed-2024-08-29-118
new file mode 100644
index 0000000..0d86d18
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-118
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-119 b/cmds/installd/tests/corpus/seed-2024-08-29-119
new file mode 100644
index 0000000..de35894
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-119
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-12 b/cmds/installd/tests/corpus/seed-2024-08-29-12
new file mode 100644
index 0000000..5565f81
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-12
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-120 b/cmds/installd/tests/corpus/seed-2024-08-29-120
new file mode 100644
index 0000000..51c0526
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-120
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-121 b/cmds/installd/tests/corpus/seed-2024-08-29-121
new file mode 100644
index 0000000..2d84c76
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-121
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-122 b/cmds/installd/tests/corpus/seed-2024-08-29-122
new file mode 100644
index 0000000..f25a7c4
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-122
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-123 b/cmds/installd/tests/corpus/seed-2024-08-29-123
new file mode 100644
index 0000000..fe8eb34
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-123
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-124 b/cmds/installd/tests/corpus/seed-2024-08-29-124
new file mode 100644
index 0000000..170e8ec
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-124
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-125 b/cmds/installd/tests/corpus/seed-2024-08-29-125
new file mode 100644
index 0000000..24e8bb8
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-125
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-126 b/cmds/installd/tests/corpus/seed-2024-08-29-126
new file mode 100644
index 0000000..92536a3
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-126
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-127 b/cmds/installd/tests/corpus/seed-2024-08-29-127
new file mode 100644
index 0000000..3a5436a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-127
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-128 b/cmds/installd/tests/corpus/seed-2024-08-29-128
new file mode 100644
index 0000000..93d131d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-128
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-129 b/cmds/installd/tests/corpus/seed-2024-08-29-129
new file mode 100644
index 0000000..842dae4
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-129
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-13 b/cmds/installd/tests/corpus/seed-2024-08-29-13
new file mode 100644
index 0000000..bc0ec3d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-13
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-130 b/cmds/installd/tests/corpus/seed-2024-08-29-130
new file mode 100644
index 0000000..9b6ed59
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-130
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-131 b/cmds/installd/tests/corpus/seed-2024-08-29-131
new file mode 100644
index 0000000..82a5d2f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-131
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-132 b/cmds/installd/tests/corpus/seed-2024-08-29-132
new file mode 100644
index 0000000..445fdc5
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-132
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-133 b/cmds/installd/tests/corpus/seed-2024-08-29-133
new file mode 100644
index 0000000..0a6e9ca
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-133
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-134 b/cmds/installd/tests/corpus/seed-2024-08-29-134
new file mode 100644
index 0000000..a359603
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-134
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-135 b/cmds/installd/tests/corpus/seed-2024-08-29-135
new file mode 100644
index 0000000..c16b303
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-135
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-136 b/cmds/installd/tests/corpus/seed-2024-08-29-136
new file mode 100644
index 0000000..f7a360f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-136
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-137 b/cmds/installd/tests/corpus/seed-2024-08-29-137
new file mode 100644
index 0000000..38a1134
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-137
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-138 b/cmds/installd/tests/corpus/seed-2024-08-29-138
new file mode 100644
index 0000000..b9db4a7
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-138
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-139 b/cmds/installd/tests/corpus/seed-2024-08-29-139
new file mode 100644
index 0000000..eb1cf93
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-139
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-14 b/cmds/installd/tests/corpus/seed-2024-08-29-14
new file mode 100644
index 0000000..74f9ad0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-14
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-140 b/cmds/installd/tests/corpus/seed-2024-08-29-140
new file mode 100644
index 0000000..0cf217c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-140
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-141 b/cmds/installd/tests/corpus/seed-2024-08-29-141
new file mode 100644
index 0000000..82763f0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-141
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-142 b/cmds/installd/tests/corpus/seed-2024-08-29-142
new file mode 100644
index 0000000..fa1d656
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-142
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-15 b/cmds/installd/tests/corpus/seed-2024-08-29-15
new file mode 100644
index 0000000..729c604
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-15
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-16 b/cmds/installd/tests/corpus/seed-2024-08-29-16
new file mode 100644
index 0000000..4dc0879
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-16
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-17 b/cmds/installd/tests/corpus/seed-2024-08-29-17
new file mode 100644
index 0000000..ac7ff13
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-17
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-18 b/cmds/installd/tests/corpus/seed-2024-08-29-18
new file mode 100644
index 0000000..2b240f4
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-18
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-19 b/cmds/installd/tests/corpus/seed-2024-08-29-19
new file mode 100644
index 0000000..a0c881b
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-19
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-2 b/cmds/installd/tests/corpus/seed-2024-08-29-2
new file mode 100644
index 0000000..2593acb
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-2
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-20 b/cmds/installd/tests/corpus/seed-2024-08-29-20
new file mode 100644
index 0000000..c55dc7f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-20
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-21 b/cmds/installd/tests/corpus/seed-2024-08-29-21
new file mode 100644
index 0000000..63d7a14
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-21
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-22 b/cmds/installd/tests/corpus/seed-2024-08-29-22
new file mode 100644
index 0000000..209f426
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-22
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-23 b/cmds/installd/tests/corpus/seed-2024-08-29-23
new file mode 100644
index 0000000..8e1775f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-23
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-24 b/cmds/installd/tests/corpus/seed-2024-08-29-24
new file mode 100644
index 0000000..4c40f3c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-24
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-25 b/cmds/installd/tests/corpus/seed-2024-08-29-25
new file mode 100644
index 0000000..d006b20
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-25
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-26 b/cmds/installd/tests/corpus/seed-2024-08-29-26
new file mode 100644
index 0000000..26893b0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-26
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-27 b/cmds/installd/tests/corpus/seed-2024-08-29-27
new file mode 100644
index 0000000..ac81138
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-27
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-28 b/cmds/installd/tests/corpus/seed-2024-08-29-28
new file mode 100644
index 0000000..71f074b
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-28
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-29 b/cmds/installd/tests/corpus/seed-2024-08-29-29
new file mode 100644
index 0000000..65dbb6d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-29
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-3 b/cmds/installd/tests/corpus/seed-2024-08-29-3
new file mode 100644
index 0000000..28ab83f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-3
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-30 b/cmds/installd/tests/corpus/seed-2024-08-29-30
new file mode 100644
index 0000000..3b96286
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-30
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-31 b/cmds/installd/tests/corpus/seed-2024-08-29-31
new file mode 100644
index 0000000..76101b3
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-31
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-32 b/cmds/installd/tests/corpus/seed-2024-08-29-32
new file mode 100644
index 0000000..79a4452
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-32
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-33 b/cmds/installd/tests/corpus/seed-2024-08-29-33
new file mode 100644
index 0000000..e6a1306
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-33
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-34 b/cmds/installd/tests/corpus/seed-2024-08-29-34
new file mode 100644
index 0000000..4a7247f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-34
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-35 b/cmds/installd/tests/corpus/seed-2024-08-29-35
new file mode 100644
index 0000000..f420b34
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-35
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-36 b/cmds/installd/tests/corpus/seed-2024-08-29-36
new file mode 100644
index 0000000..83a33ac
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-36
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-37 b/cmds/installd/tests/corpus/seed-2024-08-29-37
new file mode 100644
index 0000000..687bf06
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-37
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-38 b/cmds/installd/tests/corpus/seed-2024-08-29-38
new file mode 100644
index 0000000..40ab0ad
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-38
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-39 b/cmds/installd/tests/corpus/seed-2024-08-29-39
new file mode 100644
index 0000000..3e13978
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-39
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-4 b/cmds/installd/tests/corpus/seed-2024-08-29-4
new file mode 100644
index 0000000..8c47ea3
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-4
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-40 b/cmds/installd/tests/corpus/seed-2024-08-29-40
new file mode 100644
index 0000000..f717918
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-40
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-41 b/cmds/installd/tests/corpus/seed-2024-08-29-41
new file mode 100644
index 0000000..d9c51b9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-41
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-42 b/cmds/installd/tests/corpus/seed-2024-08-29-42
new file mode 100644
index 0000000..d806e5e
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-42
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-43 b/cmds/installd/tests/corpus/seed-2024-08-29-43
new file mode 100644
index 0000000..3bc2708
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-43
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-44 b/cmds/installd/tests/corpus/seed-2024-08-29-44
new file mode 100644
index 0000000..230839a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-44
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-45 b/cmds/installd/tests/corpus/seed-2024-08-29-45
new file mode 100644
index 0000000..40726b9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-45
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-46 b/cmds/installd/tests/corpus/seed-2024-08-29-46
new file mode 100644
index 0000000..bf56bd4
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-46
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-47 b/cmds/installd/tests/corpus/seed-2024-08-29-47
new file mode 100644
index 0000000..80cabaf
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-47
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-48 b/cmds/installd/tests/corpus/seed-2024-08-29-48
new file mode 100644
index 0000000..8f2c5f5
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-48
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-49 b/cmds/installd/tests/corpus/seed-2024-08-29-49
new file mode 100644
index 0000000..f93fbcd
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-49
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-5 b/cmds/installd/tests/corpus/seed-2024-08-29-5
new file mode 100644
index 0000000..b3f49d1
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-5
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-50 b/cmds/installd/tests/corpus/seed-2024-08-29-50
new file mode 100644
index 0000000..68912ae
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-50
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-51 b/cmds/installd/tests/corpus/seed-2024-08-29-51
new file mode 100644
index 0000000..27b315d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-51
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-52 b/cmds/installd/tests/corpus/seed-2024-08-29-52
new file mode 100644
index 0000000..159eee6
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-52
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-53 b/cmds/installd/tests/corpus/seed-2024-08-29-53
new file mode 100644
index 0000000..b07cb3c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-53
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-54 b/cmds/installd/tests/corpus/seed-2024-08-29-54
new file mode 100644
index 0000000..a5e7f2c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-54
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-55 b/cmds/installd/tests/corpus/seed-2024-08-29-55
new file mode 100644
index 0000000..bd038ad
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-55
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-56 b/cmds/installd/tests/corpus/seed-2024-08-29-56
new file mode 100644
index 0000000..8166cb8
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-56
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-57 b/cmds/installd/tests/corpus/seed-2024-08-29-57
new file mode 100644
index 0000000..fba1e2f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-57
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-58 b/cmds/installd/tests/corpus/seed-2024-08-29-58
new file mode 100644
index 0000000..f7af8f8
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-58
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-59 b/cmds/installd/tests/corpus/seed-2024-08-29-59
new file mode 100644
index 0000000..2fd68d7
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-59
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-6 b/cmds/installd/tests/corpus/seed-2024-08-29-6
new file mode 100644
index 0000000..9b02a47
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-6
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-60 b/cmds/installd/tests/corpus/seed-2024-08-29-60
new file mode 100644
index 0000000..b4c1129
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-60
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-61 b/cmds/installd/tests/corpus/seed-2024-08-29-61
new file mode 100644
index 0000000..46989aa
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-61
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-62 b/cmds/installd/tests/corpus/seed-2024-08-29-62
new file mode 100644
index 0000000..9298d0c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-62
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-63 b/cmds/installd/tests/corpus/seed-2024-08-29-63
new file mode 100644
index 0000000..326098c
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-63
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-64 b/cmds/installd/tests/corpus/seed-2024-08-29-64
new file mode 100644
index 0000000..61daf4f
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-64
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-65 b/cmds/installd/tests/corpus/seed-2024-08-29-65
new file mode 100644
index 0000000..a993900
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-65
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-66 b/cmds/installd/tests/corpus/seed-2024-08-29-66
new file mode 100644
index 0000000..85e857b
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-66
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-67 b/cmds/installd/tests/corpus/seed-2024-08-29-67
new file mode 100644
index 0000000..b775483
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-67
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-68 b/cmds/installd/tests/corpus/seed-2024-08-29-68
new file mode 100644
index 0000000..161e7ab
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-68
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-69 b/cmds/installd/tests/corpus/seed-2024-08-29-69
new file mode 100644
index 0000000..6a45dfe
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-69
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-7 b/cmds/installd/tests/corpus/seed-2024-08-29-7
new file mode 100644
index 0000000..33f61b0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-7
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-70 b/cmds/installd/tests/corpus/seed-2024-08-29-70
new file mode 100644
index 0000000..4c16b49
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-70
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-71 b/cmds/installd/tests/corpus/seed-2024-08-29-71
new file mode 100644
index 0000000..1534ce1
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-71
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-72 b/cmds/installd/tests/corpus/seed-2024-08-29-72
new file mode 100644
index 0000000..eaa5831
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-72
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-73 b/cmds/installd/tests/corpus/seed-2024-08-29-73
new file mode 100644
index 0000000..9df4a75
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-73
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-74 b/cmds/installd/tests/corpus/seed-2024-08-29-74
new file mode 100644
index 0000000..9558ac0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-74
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-75 b/cmds/installd/tests/corpus/seed-2024-08-29-75
new file mode 100644
index 0000000..a399271
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-75
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-76 b/cmds/installd/tests/corpus/seed-2024-08-29-76
new file mode 100644
index 0000000..866541d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-76
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-77 b/cmds/installd/tests/corpus/seed-2024-08-29-77
new file mode 100644
index 0000000..e3940d9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-77
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-78 b/cmds/installd/tests/corpus/seed-2024-08-29-78
new file mode 100644
index 0000000..8122306
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-78
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-79 b/cmds/installd/tests/corpus/seed-2024-08-29-79
new file mode 100644
index 0000000..0f23dfd
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-79
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-8 b/cmds/installd/tests/corpus/seed-2024-08-29-8
new file mode 100644
index 0000000..7390735
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-8
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-80 b/cmds/installd/tests/corpus/seed-2024-08-29-80
new file mode 100644
index 0000000..e3c3640
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-80
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-81 b/cmds/installd/tests/corpus/seed-2024-08-29-81
new file mode 100644
index 0000000..6c42b9e
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-81
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-82 b/cmds/installd/tests/corpus/seed-2024-08-29-82
new file mode 100644
index 0000000..09184c9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-82
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-83 b/cmds/installd/tests/corpus/seed-2024-08-29-83
new file mode 100644
index 0000000..734570a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-83
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-84 b/cmds/installd/tests/corpus/seed-2024-08-29-84
new file mode 100644
index 0000000..1a32561
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-84
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-85 b/cmds/installd/tests/corpus/seed-2024-08-29-85
new file mode 100644
index 0000000..5315dfc
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-85
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-86 b/cmds/installd/tests/corpus/seed-2024-08-29-86
new file mode 100644
index 0000000..5f798b9
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-86
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-87 b/cmds/installd/tests/corpus/seed-2024-08-29-87
new file mode 100644
index 0000000..dd1ebe1
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-87
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-88 b/cmds/installd/tests/corpus/seed-2024-08-29-88
new file mode 100644
index 0000000..45cf713
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-88
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-89 b/cmds/installd/tests/corpus/seed-2024-08-29-89
new file mode 100644
index 0000000..1053b71
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-89
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-9 b/cmds/installd/tests/corpus/seed-2024-08-29-9
new file mode 100644
index 0000000..86d511d
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-9
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-90 b/cmds/installd/tests/corpus/seed-2024-08-29-90
new file mode 100644
index 0000000..7ce82a0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-90
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-91 b/cmds/installd/tests/corpus/seed-2024-08-29-91
new file mode 100644
index 0000000..57c43d0
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-91
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-92 b/cmds/installd/tests/corpus/seed-2024-08-29-92
new file mode 100644
index 0000000..32a0f3a
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-92
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-93 b/cmds/installd/tests/corpus/seed-2024-08-29-93
new file mode 100644
index 0000000..56dcb66
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-93
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-94 b/cmds/installd/tests/corpus/seed-2024-08-29-94
new file mode 100644
index 0000000..17b5a65
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-94
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-95 b/cmds/installd/tests/corpus/seed-2024-08-29-95
new file mode 100644
index 0000000..0963039
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-95
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-96 b/cmds/installd/tests/corpus/seed-2024-08-29-96
new file mode 100644
index 0000000..1c95905
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-96
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-97 b/cmds/installd/tests/corpus/seed-2024-08-29-97
new file mode 100644
index 0000000..518910e
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-97
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-98 b/cmds/installd/tests/corpus/seed-2024-08-29-98
new file mode 100644
index 0000000..520feb2
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-98
Binary files differ
diff --git a/cmds/installd/tests/corpus/seed-2024-08-29-99 b/cmds/installd/tests/corpus/seed-2024-08-29-99
new file mode 100644
index 0000000..c1da923
--- /dev/null
+++ b/cmds/installd/tests/corpus/seed-2024-08-29-99
Binary files differ
diff --git a/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
index b1c6940..50ea0c7 100644
--- a/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
+++ b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
@@ -47,6 +47,8 @@
 } // namespace android
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    // TODO(b/183141167): need to rewrite 'dump' to avoid SIGPIPE.
+    signal(SIGPIPE, SIG_IGN);
     auto service = sp<InstalldNativeService>::make();
     fuzzService(service, FuzzedDataProvider(data, size));
     return 0;
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index ee91d80..e89543e 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -1449,7 +1449,7 @@
 
 class BootProfileTest : public ProfileTest {
   public:
-    std::vector<const std::string> extra_apps_;
+    std::vector<std::string> extra_apps_;
     std::vector<int64_t> extra_ce_data_inodes_;
 
     virtual void SetUp() {
diff --git a/cmds/installd/tests/installd_utils_test.cpp b/cmds/installd/tests/installd_utils_test.cpp
index 910cd63..19201b2 100644
--- a/cmds/installd/tests/installd_utils_test.cpp
+++ b/cmds/installd/tests/installd_utils_test.cpp
@@ -101,6 +101,9 @@
     EXPECT_EQ(0, validate_apk_path(path2))
             << path2 << " should be allowed as a valid path";
 
+    const char* path3 = TEST_APP_DIR "..example..com../example.apk";
+    EXPECT_EQ(0, validate_apk_path(path3)) << path3 << " should be allowed as a valid path";
+
     const char *badint1 = TEST_APP_DIR "../example.apk";
     EXPECT_EQ(-1, validate_apk_path(badint1))
             << badint1 << " should be rejected as a invalid path";
diff --git a/cmds/installd/utils.cpp b/cmds/installd/utils.cpp
index ffc082d..b05c655 100644
--- a/cmds/installd/utils.cpp
+++ b/cmds/installd/utils.cpp
@@ -1040,25 +1040,30 @@
         LOG(ERROR) << "Invalid directory " << dir;
         return -1;
     }
-    if (path.find("..") != std::string::npos) {
-        LOG(ERROR) << "Invalid path " << path;
-        return -1;
-    }
 
     if (path.compare(0, dir.size(), dir) != 0) {
         // Common case, path isn't under directory
         return -1;
     }
 
-    // Count number of subdirectories
-    auto pos = path.find('/', dir.size());
+    // Count number of subdirectories and invalidate ".." subdirectories
+    auto last = dir.size();
+    auto pos = path.find('/', last);
     int count = 0;
     while (pos != std::string::npos) {
-        auto next = path.find('/', pos + 1);
-        if (next > pos + 1) {
+        if (pos > last + 1) {
             count++;
         }
-        pos = next;
+        if (path.substr(last, pos - last) == "..") {
+            LOG(ERROR) << "Invalid path " << path;
+            return -1;
+        }
+        last = pos + 1;
+        pos = path.find('/', last);
+    }
+    if (path.substr(last, path.size() - last) == "..") {
+        LOG(ERROR) << "Invalid path " << path;
+        return -1;
     }
 
     if (count > maxSubdirs) {
diff --git a/cmds/lshal/libprocpartition/include/procpartition/procpartition.h b/cmds/lshal/libprocpartition/include/procpartition/procpartition.h
index ca1e690..9c0fc18 100644
--- a/cmds/lshal/libprocpartition/include/procpartition/procpartition.h
+++ b/cmds/lshal/libprocpartition/include/procpartition/procpartition.h
@@ -27,6 +27,8 @@
 enum class Partition {
     UNKNOWN = 0,
     SYSTEM,
+    SYSTEM_EXT,
+    PRODUCT,
     VENDOR,
     ODM
 };
diff --git a/cmds/lshal/libprocpartition/procpartition.cpp b/cmds/lshal/libprocpartition/procpartition.cpp
index 9645f3a..35fad57 100644
--- a/cmds/lshal/libprocpartition/procpartition.cpp
+++ b/cmds/lshal/libprocpartition/procpartition.cpp
@@ -24,6 +24,8 @@
 std::ostream& operator<<(std::ostream& os, Partition p) {
     switch (p) {
         case Partition::SYSTEM: return os << "system";
+        case Partition::SYSTEM_EXT: return os << "system_ext";
+        case Partition::PRODUCT: return os << "product";
         case Partition::VENDOR: return os << "vendor";
         case Partition::ODM: return os << "odm";
         case Partition::UNKNOWN: // fallthrough
@@ -57,6 +59,12 @@
     if (s == "system") {
         return Partition::SYSTEM;
     }
+    if (s == "system_ext") {
+        return Partition::SYSTEM_EXT;
+    }
+    if (s == "product") {
+        return Partition::PRODUCT;
+    }
     if (s == "vendor") {
         return Partition::VENDOR;
     }
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index ef2fa4d..59c4d53 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -396,7 +396,7 @@
     SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
             PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
-    *outBinder = tryGetBinder(name, true);
+    *outBinder = tryGetBinder(name, true).service;
     // returns ok regardless of result for legacy reasons
     return Status::ok();
 }
@@ -410,7 +410,16 @@
     return Status::ok();
 }
 
-Status ServiceManager::checkService(const std::string& name, os::Service* outService) {
+Status ServiceManager::checkService(const std::string& name, sp<IBinder>* outBinder) {
+    SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
+            PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
+
+    *outBinder = tryGetBinder(name, false).service;
+    // returns ok regardless of result for legacy reasons
+    return Status::ok();
+}
+
+Status ServiceManager::checkService2(const std::string& name, os::Service* outService) {
     SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
             PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
@@ -430,13 +439,15 @@
             return os::Service::make<os::Service::Tag::accessor>(nullptr);
         }
         return os::Service::make<os::Service::Tag::accessor>(
-                tryGetBinder(*accessorName, startIfNotFound));
+                tryGetBinder(*accessorName, startIfNotFound).service);
     } else {
-        return os::Service::make<os::Service::Tag::binder>(tryGetBinder(name, startIfNotFound));
+        return os::Service::make<os::Service::Tag::serviceWithMetadata>(
+                tryGetBinder(name, startIfNotFound));
     }
 }
 
-sp<IBinder> ServiceManager::tryGetBinder(const std::string& name, bool startIfNotFound) {
+os::ServiceWithMetadata ServiceManager::tryGetBinder(const std::string& name,
+                                                     bool startIfNotFound) {
     SM_PERFETTO_TRACE_FUNC(PERFETTO_TE_PROTO_FIELDS(
             PERFETTO_TE_PROTO_FIELD_CSTR(kProtoServiceName, name.c_str())));
 
@@ -450,13 +461,13 @@
         if (!service->allowIsolated && is_multiuser_uid_isolated(ctx.uid)) {
             LOG(WARNING) << "Isolated app with UID " << ctx.uid << " requested '" << name
                          << "', but the service is not allowed for isolated apps.";
-            return nullptr;
+            return os::ServiceWithMetadata();
         }
         out = service->binder;
     }
 
     if (!mAccess->canFind(ctx, name)) {
-        return nullptr;
+        return os::ServiceWithMetadata();
     }
 
     if (!out && startIfNotFound) {
@@ -473,8 +484,11 @@
         CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
         service->guaranteeClient = true;
     }
-
-    return out;
+    os::ServiceWithMetadata serviceWithMetadata = os::ServiceWithMetadata();
+    serviceWithMetadata.service = out;
+    serviceWithMetadata.isLazyService =
+            service ? service->dumpPriority & FLAG_IS_LAZY_SERVICE : false;
+    return serviceWithMetadata;
 }
 
 bool isValidServiceName(const std::string& name) {
@@ -505,8 +519,9 @@
         return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services.");
     }
 
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     if (binder == nullptr) {
@@ -888,8 +903,9 @@
     }
 
     auto ctx = mAccess->getCallingContext();
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     auto serviceIt = mNameToService.find(name);
@@ -1051,8 +1067,9 @@
     }
 
     auto ctx = mAccess->getCallingContext();
-    if (!mAccess->canAdd(ctx, name)) {
-        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
+    std::optional<std::string> accessorName;
+    if (auto status = canAddService(ctx, name, &accessorName); !status.isOk()) {
+        return status;
     }
 
     auto serviceIt = mNameToService.find(name);
@@ -1110,6 +1127,23 @@
     return Status::ok();
 }
 
+Status ServiceManager::canAddService(const Access::CallingContext& ctx, const std::string& name,
+                                     std::optional<std::string>* accessor) {
+    if (!mAccess->canAdd(ctx, name)) {
+        return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied for service.");
+    }
+#ifndef VENDORSERVICEMANAGER
+    *accessor = getVintfAccessorName(name);
+#endif
+    if (accessor->has_value()) {
+        if (!mAccess->canAdd(ctx, accessor->value())) {
+            return Status::fromExceptionCode(Status::EX_SECURITY,
+                                             "SELinux denied for the accessor of the service.");
+        }
+    }
+    return Status::ok();
+}
+
 Status ServiceManager::canFindService(const Access::CallingContext& ctx, const std::string& name,
                                       std::optional<std::string>* accessor) {
     if (!mAccess->canFind(ctx, name)) {
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 0d666c6..5c4d891 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -46,7 +46,8 @@
     // getService will try to start any services it cannot find
     binder::Status getService(const std::string& name, sp<IBinder>* outBinder) override;
     binder::Status getService2(const std::string& name, os::Service* outService) override;
-    binder::Status checkService(const std::string& name, os::Service* outService) override;
+    binder::Status checkService(const std::string& name, sp<IBinder>* outBinder) override;
+    binder::Status checkService2(const std::string& name, os::Service* outService) override;
     binder::Status addService(const std::string& name, const sp<IBinder>& binder,
                               bool allowIsolated, int32_t dumpPriority) override;
     binder::Status listServices(int32_t dumpPriority, std::vector<std::string>* outList) override;
@@ -114,7 +115,9 @@
     void removeClientCallback(const wp<IBinder>& who, ClientCallbackMap::iterator* it);
 
     os::Service tryGetService(const std::string& name, bool startIfNotFound);
-    sp<IBinder> tryGetBinder(const std::string& name, bool startIfNotFound);
+    os::ServiceWithMetadata tryGetBinder(const std::string& name, bool startIfNotFound);
+    binder::Status canAddService(const Access::CallingContext& ctx, const std::string& name,
+                                 std::optional<std::string>* accessor);
     binder::Status canFindService(const Access::CallingContext& ctx, const std::string& name,
                                   std::optional<std::string>* accessor);
 
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-0 b/cmds/servicemanager/corpus/seed-2024-08-29-0
new file mode 100644
index 0000000..fe4942e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-0
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-1 b/cmds/servicemanager/corpus/seed-2024-08-29-1
new file mode 100644
index 0000000..05c8be2
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-1
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-10 b/cmds/servicemanager/corpus/seed-2024-08-29-10
new file mode 100644
index 0000000..427dc45
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-10
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-100 b/cmds/servicemanager/corpus/seed-2024-08-29-100
new file mode 100644
index 0000000..92584e3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-100
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-101 b/cmds/servicemanager/corpus/seed-2024-08-29-101
new file mode 100644
index 0000000..4dd73ac
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-101
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-102 b/cmds/servicemanager/corpus/seed-2024-08-29-102
new file mode 100644
index 0000000..30c37a0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-102
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-103 b/cmds/servicemanager/corpus/seed-2024-08-29-103
new file mode 100644
index 0000000..76ae112
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-103
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-104 b/cmds/servicemanager/corpus/seed-2024-08-29-104
new file mode 100644
index 0000000..8ca2201
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-104
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-105 b/cmds/servicemanager/corpus/seed-2024-08-29-105
new file mode 100644
index 0000000..987fcc1
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-105
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-106 b/cmds/servicemanager/corpus/seed-2024-08-29-106
new file mode 100644
index 0000000..9f09e29
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-106
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-107 b/cmds/servicemanager/corpus/seed-2024-08-29-107
new file mode 100644
index 0000000..8f9518d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-107
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-108 b/cmds/servicemanager/corpus/seed-2024-08-29-108
new file mode 100644
index 0000000..decb38a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-108
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-109 b/cmds/servicemanager/corpus/seed-2024-08-29-109
new file mode 100644
index 0000000..e3b4426
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-109
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-11 b/cmds/servicemanager/corpus/seed-2024-08-29-11
new file mode 100644
index 0000000..177a1cd
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-11
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-110 b/cmds/servicemanager/corpus/seed-2024-08-29-110
new file mode 100644
index 0000000..35de9ca
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-110
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-111 b/cmds/servicemanager/corpus/seed-2024-08-29-111
new file mode 100644
index 0000000..ae6076f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-111
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-112 b/cmds/servicemanager/corpus/seed-2024-08-29-112
new file mode 100644
index 0000000..3d64f37
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-112
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-113 b/cmds/servicemanager/corpus/seed-2024-08-29-113
new file mode 100644
index 0000000..2b14f1d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-113
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-114 b/cmds/servicemanager/corpus/seed-2024-08-29-114
new file mode 100644
index 0000000..180831f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-114
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-115 b/cmds/servicemanager/corpus/seed-2024-08-29-115
new file mode 100644
index 0000000..71184d2
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-115
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-116 b/cmds/servicemanager/corpus/seed-2024-08-29-116
new file mode 100644
index 0000000..98c6163
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-116
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-117 b/cmds/servicemanager/corpus/seed-2024-08-29-117
new file mode 100644
index 0000000..e6dd7bb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-117
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-118 b/cmds/servicemanager/corpus/seed-2024-08-29-118
new file mode 100644
index 0000000..dd181ae
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-118
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-119 b/cmds/servicemanager/corpus/seed-2024-08-29-119
new file mode 100644
index 0000000..25de1b2
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-119
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-12 b/cmds/servicemanager/corpus/seed-2024-08-29-12
new file mode 100644
index 0000000..1312d2c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-12
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-120 b/cmds/servicemanager/corpus/seed-2024-08-29-120
new file mode 100644
index 0000000..cef973d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-120
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-121 b/cmds/servicemanager/corpus/seed-2024-08-29-121
new file mode 100644
index 0000000..7fd1df2
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-121
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-122 b/cmds/servicemanager/corpus/seed-2024-08-29-122
new file mode 100644
index 0000000..5fefc4b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-122
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-123 b/cmds/servicemanager/corpus/seed-2024-08-29-123
new file mode 100644
index 0000000..714b6b5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-123
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-124 b/cmds/servicemanager/corpus/seed-2024-08-29-124
new file mode 100644
index 0000000..925bfcc
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-124
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-125 b/cmds/servicemanager/corpus/seed-2024-08-29-125
new file mode 100644
index 0000000..6dbec24
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-125
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-126 b/cmds/servicemanager/corpus/seed-2024-08-29-126
new file mode 100644
index 0000000..d5cdcaa
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-126
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-127 b/cmds/servicemanager/corpus/seed-2024-08-29-127
new file mode 100644
index 0000000..13d0eb5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-127
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-128 b/cmds/servicemanager/corpus/seed-2024-08-29-128
new file mode 100644
index 0000000..471371c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-128
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-129 b/cmds/servicemanager/corpus/seed-2024-08-29-129
new file mode 100644
index 0000000..2908795
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-129
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-13 b/cmds/servicemanager/corpus/seed-2024-08-29-13
new file mode 100644
index 0000000..6c8bd0a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-13
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-130 b/cmds/servicemanager/corpus/seed-2024-08-29-130
new file mode 100644
index 0000000..3a64ac5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-130
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-131 b/cmds/servicemanager/corpus/seed-2024-08-29-131
new file mode 100644
index 0000000..d1da2ea
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-131
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-132 b/cmds/servicemanager/corpus/seed-2024-08-29-132
new file mode 100644
index 0000000..6de377e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-132
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-133 b/cmds/servicemanager/corpus/seed-2024-08-29-133
new file mode 100644
index 0000000..38ffcb9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-133
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-134 b/cmds/servicemanager/corpus/seed-2024-08-29-134
new file mode 100644
index 0000000..6e828ae
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-134
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-135 b/cmds/servicemanager/corpus/seed-2024-08-29-135
new file mode 100644
index 0000000..c3eb827
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-135
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-136 b/cmds/servicemanager/corpus/seed-2024-08-29-136
new file mode 100644
index 0000000..9b1fafb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-136
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-137 b/cmds/servicemanager/corpus/seed-2024-08-29-137
new file mode 100644
index 0000000..059b55b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-137
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-138 b/cmds/servicemanager/corpus/seed-2024-08-29-138
new file mode 100644
index 0000000..391bd8c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-138
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-139 b/cmds/servicemanager/corpus/seed-2024-08-29-139
new file mode 100644
index 0000000..8ea28db
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-139
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-14 b/cmds/servicemanager/corpus/seed-2024-08-29-14
new file mode 100644
index 0000000..2c704b4
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-14
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-140 b/cmds/servicemanager/corpus/seed-2024-08-29-140
new file mode 100644
index 0000000..621c536
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-140
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-141 b/cmds/servicemanager/corpus/seed-2024-08-29-141
new file mode 100644
index 0000000..1d85324
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-141
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-142 b/cmds/servicemanager/corpus/seed-2024-08-29-142
new file mode 100644
index 0000000..1df0205
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-142
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-143 b/cmds/servicemanager/corpus/seed-2024-08-29-143
new file mode 100644
index 0000000..be5ddea
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-143
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-144 b/cmds/servicemanager/corpus/seed-2024-08-29-144
new file mode 100644
index 0000000..dd7eedf
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-144
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-145 b/cmds/servicemanager/corpus/seed-2024-08-29-145
new file mode 100644
index 0000000..a9c28f9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-145
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-146 b/cmds/servicemanager/corpus/seed-2024-08-29-146
new file mode 100644
index 0000000..8e64a65
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-146
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-147 b/cmds/servicemanager/corpus/seed-2024-08-29-147
new file mode 100644
index 0000000..f65abe0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-147
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-148 b/cmds/servicemanager/corpus/seed-2024-08-29-148
new file mode 100644
index 0000000..174e50a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-148
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-149 b/cmds/servicemanager/corpus/seed-2024-08-29-149
new file mode 100644
index 0000000..3d58671
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-149
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-15 b/cmds/servicemanager/corpus/seed-2024-08-29-15
new file mode 100644
index 0000000..a1c47d3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-15
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-150 b/cmds/servicemanager/corpus/seed-2024-08-29-150
new file mode 100644
index 0000000..a41c9c8
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-150
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-151 b/cmds/servicemanager/corpus/seed-2024-08-29-151
new file mode 100644
index 0000000..013f84d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-151
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-152 b/cmds/servicemanager/corpus/seed-2024-08-29-152
new file mode 100644
index 0000000..ada2ead
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-152
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-153 b/cmds/servicemanager/corpus/seed-2024-08-29-153
new file mode 100644
index 0000000..1b56561
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-153
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-154 b/cmds/servicemanager/corpus/seed-2024-08-29-154
new file mode 100644
index 0000000..8fea50f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-154
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-155 b/cmds/servicemanager/corpus/seed-2024-08-29-155
new file mode 100644
index 0000000..ddcd8f3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-155
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-156 b/cmds/servicemanager/corpus/seed-2024-08-29-156
new file mode 100644
index 0000000..19ab7ae
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-156
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-157 b/cmds/servicemanager/corpus/seed-2024-08-29-157
new file mode 100644
index 0000000..bc89bf5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-157
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-158 b/cmds/servicemanager/corpus/seed-2024-08-29-158
new file mode 100644
index 0000000..64867f1
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-158
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-159 b/cmds/servicemanager/corpus/seed-2024-08-29-159
new file mode 100644
index 0000000..fe77d0b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-159
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-16 b/cmds/servicemanager/corpus/seed-2024-08-29-16
new file mode 100644
index 0000000..f1002d7
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-16
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-160 b/cmds/servicemanager/corpus/seed-2024-08-29-160
new file mode 100644
index 0000000..9c2123f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-160
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-161 b/cmds/servicemanager/corpus/seed-2024-08-29-161
new file mode 100644
index 0000000..0fc8e86
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-161
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-162 b/cmds/servicemanager/corpus/seed-2024-08-29-162
new file mode 100644
index 0000000..a134085
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-162
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-163 b/cmds/servicemanager/corpus/seed-2024-08-29-163
new file mode 100644
index 0000000..c23e78c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-163
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-164 b/cmds/servicemanager/corpus/seed-2024-08-29-164
new file mode 100644
index 0000000..d4feab0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-164
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-165 b/cmds/servicemanager/corpus/seed-2024-08-29-165
new file mode 100644
index 0000000..9cbdc4f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-165
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-166 b/cmds/servicemanager/corpus/seed-2024-08-29-166
new file mode 100644
index 0000000..d4cf647
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-166
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-167 b/cmds/servicemanager/corpus/seed-2024-08-29-167
new file mode 100644
index 0000000..5023909
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-167
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-168 b/cmds/servicemanager/corpus/seed-2024-08-29-168
new file mode 100644
index 0000000..846d0ec
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-168
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-169 b/cmds/servicemanager/corpus/seed-2024-08-29-169
new file mode 100644
index 0000000..cf6d882
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-169
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-17 b/cmds/servicemanager/corpus/seed-2024-08-29-17
new file mode 100644
index 0000000..6c21de8
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-17
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-170 b/cmds/servicemanager/corpus/seed-2024-08-29-170
new file mode 100644
index 0000000..d9707cb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-170
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-171 b/cmds/servicemanager/corpus/seed-2024-08-29-171
new file mode 100644
index 0000000..ea947f6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-171
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-172 b/cmds/servicemanager/corpus/seed-2024-08-29-172
new file mode 100644
index 0000000..2754437
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-172
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-173 b/cmds/servicemanager/corpus/seed-2024-08-29-173
new file mode 100644
index 0000000..96e8d56
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-173
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-174 b/cmds/servicemanager/corpus/seed-2024-08-29-174
new file mode 100644
index 0000000..aa6472e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-174
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-175 b/cmds/servicemanager/corpus/seed-2024-08-29-175
new file mode 100644
index 0000000..41e7894
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-175
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-176 b/cmds/servicemanager/corpus/seed-2024-08-29-176
new file mode 100644
index 0000000..b94712a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-176
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-177 b/cmds/servicemanager/corpus/seed-2024-08-29-177
new file mode 100644
index 0000000..4925e62
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-177
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-178 b/cmds/servicemanager/corpus/seed-2024-08-29-178
new file mode 100644
index 0000000..9ec943d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-178
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-179 b/cmds/servicemanager/corpus/seed-2024-08-29-179
new file mode 100644
index 0000000..e173bd3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-179
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-18 b/cmds/servicemanager/corpus/seed-2024-08-29-18
new file mode 100644
index 0000000..aa0b101
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-18
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-180 b/cmds/servicemanager/corpus/seed-2024-08-29-180
new file mode 100644
index 0000000..f6f4ba7
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-180
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-181 b/cmds/servicemanager/corpus/seed-2024-08-29-181
new file mode 100644
index 0000000..2ca01e6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-181
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-182 b/cmds/servicemanager/corpus/seed-2024-08-29-182
new file mode 100644
index 0000000..18966c0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-182
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-183 b/cmds/servicemanager/corpus/seed-2024-08-29-183
new file mode 100644
index 0000000..887de10
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-183
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-184 b/cmds/servicemanager/corpus/seed-2024-08-29-184
new file mode 100644
index 0000000..fee8cdb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-184
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-185 b/cmds/servicemanager/corpus/seed-2024-08-29-185
new file mode 100644
index 0000000..10dd34d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-185
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-186 b/cmds/servicemanager/corpus/seed-2024-08-29-186
new file mode 100644
index 0000000..6ad247b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-186
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-187 b/cmds/servicemanager/corpus/seed-2024-08-29-187
new file mode 100644
index 0000000..613456d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-187
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-188 b/cmds/servicemanager/corpus/seed-2024-08-29-188
new file mode 100644
index 0000000..851b25f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-188
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-189 b/cmds/servicemanager/corpus/seed-2024-08-29-189
new file mode 100644
index 0000000..c4cebe9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-189
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-19 b/cmds/servicemanager/corpus/seed-2024-08-29-19
new file mode 100644
index 0000000..c0792c0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-19
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-190 b/cmds/servicemanager/corpus/seed-2024-08-29-190
new file mode 100644
index 0000000..4370a31
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-190
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-191 b/cmds/servicemanager/corpus/seed-2024-08-29-191
new file mode 100644
index 0000000..0970428
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-191
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-192 b/cmds/servicemanager/corpus/seed-2024-08-29-192
new file mode 100644
index 0000000..6cec400
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-192
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-193 b/cmds/servicemanager/corpus/seed-2024-08-29-193
new file mode 100644
index 0000000..15a7661
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-193
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-194 b/cmds/servicemanager/corpus/seed-2024-08-29-194
new file mode 100644
index 0000000..3cabe77
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-194
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-195 b/cmds/servicemanager/corpus/seed-2024-08-29-195
new file mode 100644
index 0000000..4c5274b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-195
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-196 b/cmds/servicemanager/corpus/seed-2024-08-29-196
new file mode 100644
index 0000000..9d7a3d6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-196
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-197 b/cmds/servicemanager/corpus/seed-2024-08-29-197
new file mode 100644
index 0000000..4e69238
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-197
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-198 b/cmds/servicemanager/corpus/seed-2024-08-29-198
new file mode 100644
index 0000000..5f6df99
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-198
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-199 b/cmds/servicemanager/corpus/seed-2024-08-29-199
new file mode 100644
index 0000000..a902bba
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-199
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-2 b/cmds/servicemanager/corpus/seed-2024-08-29-2
new file mode 100644
index 0000000..ffa9719
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-2
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-20 b/cmds/servicemanager/corpus/seed-2024-08-29-20
new file mode 100644
index 0000000..2090ef6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-20
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-200 b/cmds/servicemanager/corpus/seed-2024-08-29-200
new file mode 100644
index 0000000..2c91da6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-200
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-201 b/cmds/servicemanager/corpus/seed-2024-08-29-201
new file mode 100644
index 0000000..eb77655
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-201
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-202 b/cmds/servicemanager/corpus/seed-2024-08-29-202
new file mode 100644
index 0000000..bcbe3b7
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-202
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-203 b/cmds/servicemanager/corpus/seed-2024-08-29-203
new file mode 100644
index 0000000..7c3dc94
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-203
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-204 b/cmds/servicemanager/corpus/seed-2024-08-29-204
new file mode 100644
index 0000000..a4b660e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-204
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-205 b/cmds/servicemanager/corpus/seed-2024-08-29-205
new file mode 100644
index 0000000..aee1c21
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-205
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-206 b/cmds/servicemanager/corpus/seed-2024-08-29-206
new file mode 100644
index 0000000..6863c2e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-206
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-207 b/cmds/servicemanager/corpus/seed-2024-08-29-207
new file mode 100644
index 0000000..bf2c59f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-207
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-208 b/cmds/servicemanager/corpus/seed-2024-08-29-208
new file mode 100644
index 0000000..78081b9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-208
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-209 b/cmds/servicemanager/corpus/seed-2024-08-29-209
new file mode 100644
index 0000000..76df969
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-209
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-21 b/cmds/servicemanager/corpus/seed-2024-08-29-21
new file mode 100644
index 0000000..510b9cf
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-21
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-210 b/cmds/servicemanager/corpus/seed-2024-08-29-210
new file mode 100644
index 0000000..b5174e0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-210
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-211 b/cmds/servicemanager/corpus/seed-2024-08-29-211
new file mode 100644
index 0000000..51af471
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-211
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-212 b/cmds/servicemanager/corpus/seed-2024-08-29-212
new file mode 100644
index 0000000..f260df4
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-212
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-213 b/cmds/servicemanager/corpus/seed-2024-08-29-213
new file mode 100644
index 0000000..2d322b9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-213
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-214 b/cmds/servicemanager/corpus/seed-2024-08-29-214
new file mode 100644
index 0000000..8df3af4
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-214
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-215 b/cmds/servicemanager/corpus/seed-2024-08-29-215
new file mode 100644
index 0000000..b82d03b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-215
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-216 b/cmds/servicemanager/corpus/seed-2024-08-29-216
new file mode 100644
index 0000000..16f6d4d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-216
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-217 b/cmds/servicemanager/corpus/seed-2024-08-29-217
new file mode 100644
index 0000000..d4c2bb3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-217
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-218 b/cmds/servicemanager/corpus/seed-2024-08-29-218
new file mode 100644
index 0000000..d0c1970
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-218
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-219 b/cmds/servicemanager/corpus/seed-2024-08-29-219
new file mode 100644
index 0000000..75edd86
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-219
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-22 b/cmds/servicemanager/corpus/seed-2024-08-29-22
new file mode 100644
index 0000000..aa87441
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-22
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-220 b/cmds/servicemanager/corpus/seed-2024-08-29-220
new file mode 100644
index 0000000..b3b6788
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-220
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-221 b/cmds/servicemanager/corpus/seed-2024-08-29-221
new file mode 100644
index 0000000..429da0e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-221
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-222 b/cmds/servicemanager/corpus/seed-2024-08-29-222
new file mode 100644
index 0000000..be8e3f3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-222
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-223 b/cmds/servicemanager/corpus/seed-2024-08-29-223
new file mode 100644
index 0000000..a5a6d9c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-223
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-224 b/cmds/servicemanager/corpus/seed-2024-08-29-224
new file mode 100644
index 0000000..9a7d07e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-224
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-225 b/cmds/servicemanager/corpus/seed-2024-08-29-225
new file mode 100644
index 0000000..39a5644
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-225
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-226 b/cmds/servicemanager/corpus/seed-2024-08-29-226
new file mode 100644
index 0000000..c32f26a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-226
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-227 b/cmds/servicemanager/corpus/seed-2024-08-29-227
new file mode 100644
index 0000000..5af105b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-227
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-23 b/cmds/servicemanager/corpus/seed-2024-08-29-23
new file mode 100644
index 0000000..4399c39
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-23
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-24 b/cmds/servicemanager/corpus/seed-2024-08-29-24
new file mode 100644
index 0000000..133c59a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-24
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-25 b/cmds/servicemanager/corpus/seed-2024-08-29-25
new file mode 100644
index 0000000..ec1ac02
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-25
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-26 b/cmds/servicemanager/corpus/seed-2024-08-29-26
new file mode 100644
index 0000000..55397b9
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-26
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-27 b/cmds/servicemanager/corpus/seed-2024-08-29-27
new file mode 100644
index 0000000..517af0b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-27
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-28 b/cmds/servicemanager/corpus/seed-2024-08-29-28
new file mode 100644
index 0000000..0401668
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-28
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-29 b/cmds/servicemanager/corpus/seed-2024-08-29-29
new file mode 100644
index 0000000..05ad4ec
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-29
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-3 b/cmds/servicemanager/corpus/seed-2024-08-29-3
new file mode 100644
index 0000000..14dcdd0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-3
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-30 b/cmds/servicemanager/corpus/seed-2024-08-29-30
new file mode 100644
index 0000000..d941024
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-30
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-31 b/cmds/servicemanager/corpus/seed-2024-08-29-31
new file mode 100644
index 0000000..e93a192
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-31
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-32 b/cmds/servicemanager/corpus/seed-2024-08-29-32
new file mode 100644
index 0000000..36f82dd
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-32
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-33 b/cmds/servicemanager/corpus/seed-2024-08-29-33
new file mode 100644
index 0000000..5f64227
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-33
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-34 b/cmds/servicemanager/corpus/seed-2024-08-29-34
new file mode 100644
index 0000000..13f7634
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-34
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-35 b/cmds/servicemanager/corpus/seed-2024-08-29-35
new file mode 100644
index 0000000..3a4476e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-35
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-36 b/cmds/servicemanager/corpus/seed-2024-08-29-36
new file mode 100644
index 0000000..da9c208
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-36
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-37 b/cmds/servicemanager/corpus/seed-2024-08-29-37
new file mode 100644
index 0000000..969a957
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-37
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-38 b/cmds/servicemanager/corpus/seed-2024-08-29-38
new file mode 100644
index 0000000..ab6f106
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-38
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-39 b/cmds/servicemanager/corpus/seed-2024-08-29-39
new file mode 100644
index 0000000..248a549
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-39
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-4 b/cmds/servicemanager/corpus/seed-2024-08-29-4
new file mode 100644
index 0000000..0bd7cd5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-4
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-40 b/cmds/servicemanager/corpus/seed-2024-08-29-40
new file mode 100644
index 0000000..7031a91
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-40
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-41 b/cmds/servicemanager/corpus/seed-2024-08-29-41
new file mode 100644
index 0000000..8b8925c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-41
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-42 b/cmds/servicemanager/corpus/seed-2024-08-29-42
new file mode 100644
index 0000000..c6e2167
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-42
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-43 b/cmds/servicemanager/corpus/seed-2024-08-29-43
new file mode 100644
index 0000000..671a821
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-43
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-44 b/cmds/servicemanager/corpus/seed-2024-08-29-44
new file mode 100644
index 0000000..7c365b0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-44
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-45 b/cmds/servicemanager/corpus/seed-2024-08-29-45
new file mode 100644
index 0000000..a38d138
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-45
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-46 b/cmds/servicemanager/corpus/seed-2024-08-29-46
new file mode 100644
index 0000000..62acb77
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-46
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-47 b/cmds/servicemanager/corpus/seed-2024-08-29-47
new file mode 100644
index 0000000..aea84c6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-47
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-48 b/cmds/servicemanager/corpus/seed-2024-08-29-48
new file mode 100644
index 0000000..a5bab7c
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-48
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-49 b/cmds/servicemanager/corpus/seed-2024-08-29-49
new file mode 100644
index 0000000..4f19f09
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-49
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-5 b/cmds/servicemanager/corpus/seed-2024-08-29-5
new file mode 100644
index 0000000..4e8a853
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-5
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-50 b/cmds/servicemanager/corpus/seed-2024-08-29-50
new file mode 100644
index 0000000..2f1d78b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-50
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-51 b/cmds/servicemanager/corpus/seed-2024-08-29-51
new file mode 100644
index 0000000..7a44b4a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-51
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-52 b/cmds/servicemanager/corpus/seed-2024-08-29-52
new file mode 100644
index 0000000..3da177b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-52
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-53 b/cmds/servicemanager/corpus/seed-2024-08-29-53
new file mode 100644
index 0000000..c67df71
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-53
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-54 b/cmds/servicemanager/corpus/seed-2024-08-29-54
new file mode 100644
index 0000000..b1e8fec
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-54
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-55 b/cmds/servicemanager/corpus/seed-2024-08-29-55
new file mode 100644
index 0000000..20b268a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-55
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-56 b/cmds/servicemanager/corpus/seed-2024-08-29-56
new file mode 100644
index 0000000..1461926
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-56
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-57 b/cmds/servicemanager/corpus/seed-2024-08-29-57
new file mode 100644
index 0000000..fab8065
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-57
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-58 b/cmds/servicemanager/corpus/seed-2024-08-29-58
new file mode 100644
index 0000000..676f9e4
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-58
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-59 b/cmds/servicemanager/corpus/seed-2024-08-29-59
new file mode 100644
index 0000000..a8e2c72
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-59
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-6 b/cmds/servicemanager/corpus/seed-2024-08-29-6
new file mode 100644
index 0000000..585f1f0
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-6
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-60 b/cmds/servicemanager/corpus/seed-2024-08-29-60
new file mode 100644
index 0000000..ef4b098
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-60
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-61 b/cmds/servicemanager/corpus/seed-2024-08-29-61
new file mode 100644
index 0000000..5f45443
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-61
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-62 b/cmds/servicemanager/corpus/seed-2024-08-29-62
new file mode 100644
index 0000000..7ffd776
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-62
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-63 b/cmds/servicemanager/corpus/seed-2024-08-29-63
new file mode 100644
index 0000000..fa026cd
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-63
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-64 b/cmds/servicemanager/corpus/seed-2024-08-29-64
new file mode 100644
index 0000000..422c823
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-64
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-65 b/cmds/servicemanager/corpus/seed-2024-08-29-65
new file mode 100644
index 0000000..c811c44
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-65
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-66 b/cmds/servicemanager/corpus/seed-2024-08-29-66
new file mode 100644
index 0000000..8407da2
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-66
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-67 b/cmds/servicemanager/corpus/seed-2024-08-29-67
new file mode 100644
index 0000000..76dfdc3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-67
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-68 b/cmds/servicemanager/corpus/seed-2024-08-29-68
new file mode 100644
index 0000000..d93e0e3
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-68
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-69 b/cmds/servicemanager/corpus/seed-2024-08-29-69
new file mode 100644
index 0000000..12b501b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-69
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-7 b/cmds/servicemanager/corpus/seed-2024-08-29-7
new file mode 100644
index 0000000..6478363
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-7
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-70 b/cmds/servicemanager/corpus/seed-2024-08-29-70
new file mode 100644
index 0000000..e620623
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-70
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-71 b/cmds/servicemanager/corpus/seed-2024-08-29-71
new file mode 100644
index 0000000..dc32a5f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-71
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-72 b/cmds/servicemanager/corpus/seed-2024-08-29-72
new file mode 100644
index 0000000..24217c6
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-72
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-73 b/cmds/servicemanager/corpus/seed-2024-08-29-73
new file mode 100644
index 0000000..a9a0b2b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-73
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-74 b/cmds/servicemanager/corpus/seed-2024-08-29-74
new file mode 100644
index 0000000..fd8a429
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-74
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-75 b/cmds/servicemanager/corpus/seed-2024-08-29-75
new file mode 100644
index 0000000..090b489
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-75
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-76 b/cmds/servicemanager/corpus/seed-2024-08-29-76
new file mode 100644
index 0000000..c92c45f
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-76
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-77 b/cmds/servicemanager/corpus/seed-2024-08-29-77
new file mode 100644
index 0000000..002a233
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-77
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-78 b/cmds/servicemanager/corpus/seed-2024-08-29-78
new file mode 100644
index 0000000..633f937
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-78
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-79 b/cmds/servicemanager/corpus/seed-2024-08-29-79
new file mode 100644
index 0000000..7778240
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-79
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-8 b/cmds/servicemanager/corpus/seed-2024-08-29-8
new file mode 100644
index 0000000..580e200
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-8
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-80 b/cmds/servicemanager/corpus/seed-2024-08-29-80
new file mode 100644
index 0000000..90d74e4
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-80
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-81 b/cmds/servicemanager/corpus/seed-2024-08-29-81
new file mode 100644
index 0000000..1fd7668
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-81
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-82 b/cmds/servicemanager/corpus/seed-2024-08-29-82
new file mode 100644
index 0000000..d771501
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-82
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-83 b/cmds/servicemanager/corpus/seed-2024-08-29-83
new file mode 100644
index 0000000..6a4a1ca
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-83
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-84 b/cmds/servicemanager/corpus/seed-2024-08-29-84
new file mode 100644
index 0000000..bf8459b
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-84
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-85 b/cmds/servicemanager/corpus/seed-2024-08-29-85
new file mode 100644
index 0000000..8c88cac
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-85
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-86 b/cmds/servicemanager/corpus/seed-2024-08-29-86
new file mode 100644
index 0000000..62f6765
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-86
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-87 b/cmds/servicemanager/corpus/seed-2024-08-29-87
new file mode 100644
index 0000000..eb54dcb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-87
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-88 b/cmds/servicemanager/corpus/seed-2024-08-29-88
new file mode 100644
index 0000000..f38aaba
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-88
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-89 b/cmds/servicemanager/corpus/seed-2024-08-29-89
new file mode 100644
index 0000000..b4154ae
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-89
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-9 b/cmds/servicemanager/corpus/seed-2024-08-29-9
new file mode 100644
index 0000000..5dca38a
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-9
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-90 b/cmds/servicemanager/corpus/seed-2024-08-29-90
new file mode 100644
index 0000000..2725a79
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-90
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-91 b/cmds/servicemanager/corpus/seed-2024-08-29-91
new file mode 100644
index 0000000..9140e28
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-91
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-92 b/cmds/servicemanager/corpus/seed-2024-08-29-92
new file mode 100644
index 0000000..88dda1e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-92
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-93 b/cmds/servicemanager/corpus/seed-2024-08-29-93
new file mode 100644
index 0000000..6dd114e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-93
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-94 b/cmds/servicemanager/corpus/seed-2024-08-29-94
new file mode 100644
index 0000000..462c185
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-94
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-95 b/cmds/servicemanager/corpus/seed-2024-08-29-95
new file mode 100644
index 0000000..4472deb
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-95
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-96 b/cmds/servicemanager/corpus/seed-2024-08-29-96
new file mode 100644
index 0000000..875efc5
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-96
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-97 b/cmds/servicemanager/corpus/seed-2024-08-29-97
new file mode 100644
index 0000000..3f0277e
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-97
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-98 b/cmds/servicemanager/corpus/seed-2024-08-29-98
new file mode 100644
index 0000000..2c66436
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-98
Binary files differ
diff --git a/cmds/servicemanager/corpus/seed-2024-08-29-99 b/cmds/servicemanager/corpus/seed-2024-08-29-99
new file mode 100644
index 0000000..9a6ff1d
--- /dev/null
+++ b/cmds/servicemanager/corpus/seed-2024-08-29-99
Binary files differ
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 95f459f..7ad84fa 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -92,6 +92,11 @@
     auto sm = getPermissiveServiceManager();
     EXPECT_TRUE(sm->addService("foo", getBinder(), false /*allowIsolated*/,
         IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk());
+
+    EXPECT_TRUE(sm->addService("lazyfoo", getBinder(), false /*allowIsolated*/,
+                               IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT |
+                                       IServiceManager::FLAG_IS_LAZY_SERVICE)
+                        .isOk());
 }
 
 TEST(AddService, EmptyNameDisallowed) {
@@ -156,7 +161,7 @@
 
     Service outA;
     EXPECT_TRUE(sm->getService2("foo", &outA).isOk());
-    EXPECT_EQ(serviceA, outA.get<Service::Tag::binder>());
+    EXPECT_EQ(serviceA, outA.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinderA;
     EXPECT_TRUE(sm->getService("foo", &outBinderA).isOk());
     EXPECT_EQ(serviceA, outBinderA);
@@ -168,7 +173,7 @@
 
     Service outB;
     EXPECT_TRUE(sm->getService2("foo", &outB).isOk());
-    EXPECT_EQ(serviceB, outB.get<Service::Tag::binder>());
+    EXPECT_EQ(serviceB, outB.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinderB;
     EXPECT_TRUE(sm->getService("foo", &outBinderB).isOk());
     EXPECT_EQ(serviceB, outBinderB);
@@ -195,10 +200,15 @@
 
     Service out;
     EXPECT_TRUE(sm->getService2("foo", &out).isOk());
-    EXPECT_EQ(service, out.get<Service::Tag::binder>());
+    EXPECT_EQ(service, out.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinder;
     EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
     EXPECT_EQ(service, outBinder);
+
+    EXPECT_TRUE(sm->checkService2("foo", &out).isOk());
+    EXPECT_EQ(service, out.get<Service::Tag::serviceWithMetadata>().service);
+    EXPECT_TRUE(sm->checkService("foo", &outBinder).isOk());
+    EXPECT_EQ(service, outBinder);
 }
 
 TEST(GetService, NonExistant) {
@@ -206,7 +216,7 @@
 
     Service out;
     EXPECT_TRUE(sm->getService2("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinder;
     EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
     EXPECT_EQ(nullptr, outBinder);
@@ -227,7 +237,7 @@
     Service out;
     // returns nullptr but has OK status for legacy compatibility
     EXPECT_TRUE(sm->getService2("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinder;
     EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
     EXPECT_EQ(nullptr, outBinder);
@@ -257,7 +267,7 @@
 
     Service out;
     EXPECT_TRUE(sm->getService2("foo", &out).isOk());
-    EXPECT_EQ(service, out.get<Service::Tag::binder>());
+    EXPECT_EQ(service, out.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinder;
     EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
     EXPECT_EQ(service, outBinder);
@@ -289,7 +299,7 @@
     Service out;
     // returns nullptr but has OK status for legacy compatibility
     EXPECT_TRUE(sm->getService2("foo", &out).isOk());
-    EXPECT_EQ(nullptr, out.get<Service::Tag::binder>());
+    EXPECT_EQ(nullptr, out.get<Service::Tag::serviceWithMetadata>().service);
     sp<IBinder> outBinder;
     EXPECT_TRUE(sm->getService("foo", &outBinder).isOk());
     EXPECT_EQ(nullptr, outBinder);
diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp
index c19c9da..a91a7dc 100644
--- a/cmds/sfdo/Android.bp
+++ b/cmds/sfdo/Android.bp
@@ -1,17 +1,10 @@
-cc_binary {
+rust_binary {
     name: "sfdo",
+    srcs: ["sfdo.rs"],
 
-    srcs: ["sfdo.cpp"],
-
-    shared_libs: [
-        "libutils",
-        "libgui",
+    rustlibs: [
+        "android.gui-rust",
+        "libclap",
     ],
-
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-Wunused",
-        "-Wunreachable-code",
-    ],
+    edition: "2021",
 }
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
deleted file mode 100644
index de0e171..0000000
--- a/cmds/sfdo/sfdo.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <inttypes.h>
-#include <stdint.h>
-#include <any>
-#include <map>
-
-#include <cutils/properties.h>
-#include <sys/resource.h>
-#include <utils/Log.h>
-
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <gui/SurfaceControl.h>
-#include <private/gui/ComposerServiceAIDL.h>
-
-using namespace android;
-
-std::map<std::string, std::any> g_functions;
-
-enum class ParseToggleResult {
-    kError,
-    kFalse,
-    kTrue,
-};
-
-const std::map<std::string, std::string> g_function_details = {
-        {"debugFlash", "[optional(delay)] Perform a debug flash."},
-        {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
-        {"scheduleComposite", "Force composite ahead of next VSYNC."},
-        {"scheduleCommit", "Force commit ahead of next VSYNC."},
-        {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
-        {"forceClientComposition",
-         "[enabled | disabled] When enabled, it disables "
-         "Hardware Overlays, and routes all window composition to the GPU. This can "
-         "help check if there is a bug in HW Composer."},
-};
-
-static void ShowUsage() {
-    std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
-    for (const auto& sf : g_functions) {
-        const std::string fn = sf.first;
-        std::string fdetails = "TODO";
-        if (g_function_details.find(fn) != g_function_details.end())
-            fdetails = g_function_details.find(fn)->second;
-        std::cout << "    " << fn << ": " << fdetails << "\n";
-    }
-}
-
-// Returns 1 for positive keywords and 0 for negative keywords.
-// If the string does not match any it will return -1.
-ParseToggleResult parseToggle(const char* str) {
-    const std::unordered_set<std::string> positive{"1",  "true",    "y",   "yes",
-                                                   "on", "enabled", "show"};
-    const std::unordered_set<std::string> negative{"0",   "false",    "n",   "no",
-                                                   "off", "disabled", "hide"};
-
-    const std::string word(str);
-    if (positive.count(word)) {
-        return ParseToggleResult::kTrue;
-    }
-    if (negative.count(word)) {
-        return ParseToggleResult::kFalse;
-    }
-
-    return ParseToggleResult::kError;
-}
-
-int frameRateIndicator(int argc, char** argv) {
-    bool hide = false, show = false;
-    if (argc == 3) {
-        show = strcmp(argv[2], "show") == 0;
-        hide = strcmp(argv[2], "hide") == 0;
-    }
-
-    if (show || hide) {
-        ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
-    } else {
-        std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
-        return -1;
-    }
-    return 0;
-}
-
-int debugFlash(int argc, char** argv) {
-    int delay = 0;
-    if (argc == 3) {
-        delay = atoi(argv[2]) == 0;
-    }
-
-    ComposerServiceAIDL::getComposerService()->setDebugFlash(delay);
-    return 0;
-}
-
-int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleComposite();
-    return 0;
-}
-
-int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
-    ComposerServiceAIDL::getComposerService()->scheduleCommit();
-    return 0;
-}
-
-int forceClientComposition(int argc, char** argv) {
-    bool enabled = true;
-    // A valid command looks like this:
-    // adb shell sfdo forceClientComposition enabled
-    if (argc >= 3) {
-        const ParseToggleResult toggle = parseToggle(argv[2]);
-        if (toggle == ParseToggleResult::kError) {
-            std::cerr << "Incorrect usage of forceClientComposition. "
-                         "Missing [enabled | disabled].\n";
-            return -1;
-        }
-        if (argc > 3) {
-            std::cerr << "Too many arguments after [enabled | disabled]. "
-                         "Ignoring extra arguments.\n";
-        }
-        enabled = (toggle == ParseToggleResult::kTrue);
-    } else {
-        std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
-        return -1;
-    }
-
-    ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
-    return 0;
-}
-
-int main(int argc, char** argv) {
-    std::cout << "Execute SurfaceFlinger internal commands.\n";
-    std::cout << "sfdo requires to be run with root permissions..\n";
-
-    g_functions["frameRateIndicator"] = frameRateIndicator;
-    g_functions["debugFlash"] = debugFlash;
-    g_functions["scheduleComposite"] = scheduleComposite;
-    g_functions["scheduleCommit"] = scheduleCommit;
-    g_functions["forceClientComposition"] = forceClientComposition;
-
-    if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
-        std::cout << "Running: " << argv[1] << "\n";
-        const std::string key(argv[1]);
-        const auto fn = g_functions[key];
-        int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv);
-        if (result == 0) {
-            std::cout << "Success.\n";
-        }
-        return result;
-    } else {
-        ShowUsage();
-    }
-    return 0;
-}
\ No newline at end of file
diff --git a/cmds/sfdo/sfdo.rs b/cmds/sfdo/sfdo.rs
new file mode 100644
index 0000000..863df6b
--- /dev/null
+++ b/cmds/sfdo/sfdo.rs
@@ -0,0 +1,155 @@
+// Copyright (C) 2024 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.
+
+//! sfdo: Make surface flinger do things
+use android_gui::{aidl::android::gui::ISurfaceComposer::ISurfaceComposer, binder};
+use clap::{Parser, Subcommand};
+use std::fmt::Debug;
+
+const SERVICE_IDENTIFIER: &str = "SurfaceFlingerAIDL";
+
+fn print_result<T, E>(function_name: &str, res: Result<T, E>)
+where
+    E: Debug,
+{
+    match res {
+        Ok(_) => println!("{}: Operation successful!", function_name),
+        Err(err) => println!("{}: Operation failed: {:?}", function_name, err),
+    }
+}
+
+fn parse_toggle(toggle_value: &str) -> Option<bool> {
+    let positive = ["1", "true", "y", "yes", "on", "enabled", "show"];
+    let negative = ["0", "false", "n", "no", "off", "disabled", "hide"];
+
+    let word = toggle_value.to_lowercase(); // Case-insensitive comparison
+
+    if positive.contains(&word.as_str()) {
+        Some(true)
+    } else if negative.contains(&word.as_str()) {
+        Some(false)
+    } else {
+        None
+    }
+}
+
+#[derive(Parser)]
+#[command(version = "0.1", about = "Execute SurfaceFlinger internal commands.")]
+#[command(propagate_version = true)]
+struct Cli {
+    #[command(subcommand)]
+    command: Option<Commands>,
+}
+
+#[derive(Subcommand, Debug)]
+enum Commands {
+    #[command(about = "[optional(--delay)] Perform a debug flash.")]
+    DebugFlash {
+        #[arg(short, long, default_value_t = 0)]
+        delay: i32,
+    },
+
+    #[command(
+        about = "state = [enabled | disabled] When enabled, it disables Hardware Overlays, \
+                      and routes all window composition to the GPU. This can help check if \
+                      there is a bug in HW Composer."
+    )]
+    ForceClientComposition { state: Option<String> },
+
+    #[command(about = "state = [hide | show], displays the framerate in the top left corner.")]
+    FrameRateIndicator { state: Option<String> },
+
+    #[command(about = "Force composite ahead of next VSYNC.")]
+    ScheduleComposite,
+
+    #[command(about = "Force commit ahead of next VSYNC.")]
+    ScheduleCommit,
+}
+
+/// sfdo command line tool
+///
+/// sfdo allows you to call different functions from the SurfaceComposer using
+/// the adb shell.
+fn main() {
+    binder::ProcessState::start_thread_pool();
+    let composer_service = match binder::get_interface::<dyn ISurfaceComposer>(SERVICE_IDENTIFIER) {
+        Ok(service) => service,
+        Err(err) => {
+            eprintln!("Unable to connect to ISurfaceComposer: {}", err);
+            return;
+        }
+    };
+
+    let cli = Cli::parse();
+
+    match &cli.command {
+        Some(Commands::FrameRateIndicator { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.enableRefreshRateOverlay(true);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.enableRefreshRateOverlay(false);
+                        print_result("enableRefreshRateOverlay", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [hide | show]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [hide | show]");
+            }
+        }
+        Some(Commands::DebugFlash { delay }) => {
+            let res = composer_service.setDebugFlash(*delay);
+            print_result("setDebugFlash", res);
+        }
+        Some(Commands::ScheduleComposite) => {
+            let res = composer_service.scheduleComposite();
+            print_result("scheduleComposite", res);
+        }
+        Some(Commands::ScheduleCommit) => {
+            let res = composer_service.scheduleCommit();
+            print_result("scheduleCommit", res);
+        }
+        Some(Commands::ForceClientComposition { state }) => {
+            if let Some(op_state) = state {
+                let toggle = parse_toggle(op_state);
+                match toggle {
+                    Some(true) => {
+                        let res = composer_service.forceClientComposition(true);
+                        print_result("forceClientComposition", res);
+                    }
+                    Some(false) => {
+                        let res = composer_service.forceClientComposition(false);
+                        print_result("forceClientComposition", res);
+                    }
+                    None => {
+                        eprintln!("Invalid state: {}, choices are [enabled | disabled]", op_state);
+                    }
+                }
+            } else {
+                eprintln!("No state, choices are [enabled | disabled]");
+            }
+        }
+        None => {
+            println!("Execute SurfaceFlinger internal commands.");
+            println!("run `adb shell sfdo help` for more to view the commands.");
+            println!("run `adb shell sfdo [COMMAND] --help` for more info on the command.");
+        }
+    }
+}
diff --git a/cmds/surfacereplayer/replayer/Replayer.cpp b/cmds/surfacereplayer/replayer/Replayer.cpp
deleted file mode 100644
index 44235cc..0000000
--- a/cmds/surfacereplayer/replayer/Replayer.cpp
+++ /dev/null
@@ -1,707 +0,0 @@
-/* Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SurfaceReplayer"
-
-#include "Replayer.h"
-
-#include <android/native_window.h>
-
-#include <android-base/file.h>
-
-#include <gui/BufferQueue.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/LayerState.h>
-#include <gui/Surface.h>
-#include <private/gui/ComposerService.h>
-
-#include <utils/Log.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-
-#include <chrono>
-#include <cmath>
-#include <condition_variable>
-#include <cstdlib>
-#include <fstream>
-#include <functional>
-#include <iostream>
-#include <mutex>
-#include <sstream>
-#include <string>
-#include <thread>
-#include <vector>
-
-using namespace android;
-
-std::atomic_bool Replayer::sReplayingManually(false);
-
-Replayer::Replayer(const std::string& filename, bool replayManually, int numThreads, bool wait,
-        nsecs_t stopHere)
-      : mTrace(),
-        mLoaded(false),
-        mIncrementIndex(0),
-        mCurrentTime(0),
-        mNumThreads(numThreads),
-        mWaitForTimeStamps(wait),
-        mStopTimeStamp(stopHere) {
-    srand(RAND_COLOR_SEED);
-
-    std::string input;
-    if (!android::base::ReadFileToString(filename, &input, true)) {
-        std::cerr << "Trace did not load. Does " << filename << " exist?" << std::endl;
-        abort();
-    }
-
-    mLoaded = mTrace.ParseFromString(input);
-    if (!mLoaded) {
-        std::cerr << "Trace did not load." << std::endl;
-        abort();
-    }
-
-    mCurrentTime = mTrace.increment(0).time_stamp();
-
-    sReplayingManually.store(replayManually);
-
-    if (stopHere < 0) {
-        mHasStopped = true;
-    }
-}
-
-Replayer::Replayer(const Trace& t, bool replayManually, int numThreads, bool wait, nsecs_t stopHere)
-      : mTrace(t),
-        mLoaded(true),
-        mIncrementIndex(0),
-        mCurrentTime(0),
-        mNumThreads(numThreads),
-        mWaitForTimeStamps(wait),
-        mStopTimeStamp(stopHere) {
-    srand(RAND_COLOR_SEED);
-    mCurrentTime = mTrace.increment(0).time_stamp();
-
-    sReplayingManually.store(replayManually);
-
-    if (stopHere < 0) {
-        mHasStopped = true;
-    }
-}
-
-status_t Replayer::replay() {
-    signal(SIGINT, Replayer::stopAutoReplayHandler); //for manual control
-
-    ALOGV("There are %d increments.", mTrace.increment_size());
-
-    status_t status = loadSurfaceComposerClient();
-
-    if (status != NO_ERROR) {
-        ALOGE("Couldn't create SurfaceComposerClient (%d)", status);
-        return status;
-    }
-
-    SurfaceComposerClient::enableVSyncInjections(true);
-
-    initReplay();
-
-    ALOGV("Starting actual Replay!");
-    while (!mPendingIncrements.empty()) {
-        mCurrentIncrement = mTrace.increment(mIncrementIndex);
-
-        if (mHasStopped == false && mCurrentIncrement.time_stamp() >= mStopTimeStamp) {
-            mHasStopped = true;
-            sReplayingManually.store(true);
-        }
-
-        waitForConsoleCommmand();
-
-        if (mWaitForTimeStamps) {
-            waitUntilTimestamp(mCurrentIncrement.time_stamp());
-        }
-
-        auto event = mPendingIncrements.front();
-        mPendingIncrements.pop();
-
-        event->complete();
-
-        if (event->getIncrementType() == Increment::kVsyncEvent) {
-            mWaitingForNextVSync = false;
-        }
-
-        if (mIncrementIndex + mNumThreads < mTrace.increment_size()) {
-            status = dispatchEvent(mIncrementIndex + mNumThreads);
-
-            if (status != NO_ERROR) {
-                SurfaceComposerClient::enableVSyncInjections(false);
-                return status;
-            }
-        }
-
-        mIncrementIndex++;
-        mCurrentTime = mCurrentIncrement.time_stamp();
-    }
-
-    SurfaceComposerClient::enableVSyncInjections(false);
-
-    return status;
-}
-
-status_t Replayer::initReplay() {
-    for (int i = 0; i < mNumThreads && i < mTrace.increment_size(); i++) {
-        status_t status = dispatchEvent(i);
-
-        if (status != NO_ERROR) {
-            ALOGE("Unable to dispatch event (%d)", status);
-            return status;
-        }
-    }
-
-    return NO_ERROR;
-}
-
-void Replayer::stopAutoReplayHandler(int /*signal*/) {
-    if (sReplayingManually) {
-        SurfaceComposerClient::enableVSyncInjections(false);
-        exit(0);
-    }
-
-    sReplayingManually.store(true);
-}
-
-std::vector<std::string> split(const std::string& s, const char delim) {
-    std::vector<std::string> elems;
-    std::stringstream ss(s);
-    std::string item;
-    while (getline(ss, item, delim)) {
-        elems.push_back(item);
-    }
-    return elems;
-}
-
-bool isNumber(const std::string& s) {
-    return !s.empty() &&
-           std::find_if(s.begin(), s.end(), [](char c) { return !std::isdigit(c); }) == s.end();
-}
-
-void Replayer::waitForConsoleCommmand() {
-    if (!sReplayingManually || mWaitingForNextVSync) {
-        return;
-    }
-
-    while (true) {
-        std::string input = "";
-        std::cout << "> ";
-        getline(std::cin, input);
-
-        if (input.empty()) {
-            input = mLastInput;
-        } else {
-            mLastInput = input;
-        }
-
-        if (mLastInput.empty()) {
-            continue;
-        }
-
-        std::vector<std::string> inputs = split(input, ' ');
-
-        if (inputs[0] == "n") {  // next vsync
-            mWaitingForNextVSync = true;
-            break;
-
-        } else if (inputs[0] == "ni") {  // next increment
-            break;
-
-        } else if (inputs[0] == "c") {  // continue
-            if (inputs.size() > 1 && isNumber(inputs[1])) {
-                long milliseconds = stoi(inputs[1]);
-                std::thread([&] {
-                    std::cout << "Started!" << std::endl;
-                    std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
-                    sReplayingManually.store(true);
-                    std::cout << "Should have stopped!" << std::endl;
-                }).detach();
-            }
-            sReplayingManually.store(false);
-            mWaitingForNextVSync = false;
-            break;
-
-        } else if (inputs[0] == "s") {  // stop at this timestamp
-            if (inputs.size() < 1) {
-                std::cout << "No time stamp given" << std::endl;
-                continue;
-            }
-            sReplayingManually.store(false);
-            mStopTimeStamp = stol(inputs[1]);
-            mHasStopped = false;
-            break;
-        } else if (inputs[0] == "l") {  // list
-            std::cout << "Time stamp: " << mCurrentIncrement.time_stamp() << "\n";
-            continue;
-        } else if (inputs[0] == "q") {  // quit
-            SurfaceComposerClient::enableVSyncInjections(false);
-            exit(0);
-
-        } else if (inputs[0] == "h") {  // help
-                                        // add help menu
-            std::cout << "Manual Replay options:\n";
-            std::cout << " n  - Go to next VSync\n";
-            std::cout << " ni - Go to next increment\n";
-            std::cout << " c  - Continue\n";
-            std::cout << " c [milliseconds] - Continue until specified number of milliseconds\n";
-            std::cout << " s [timestamp]    - Continue and stop at specified timestamp\n";
-            std::cout << " l  - List out timestamp of current increment\n";
-            std::cout << " h  - Display help menu\n";
-            std::cout << std::endl;
-            continue;
-        }
-
-        std::cout << "Invalid Command" << std::endl;
-    }
-}
-
-status_t Replayer::dispatchEvent(int index) {
-    auto increment = mTrace.increment(index);
-    std::shared_ptr<Event> event = std::make_shared<Event>(increment.increment_case());
-    mPendingIncrements.push(event);
-
-    status_t status = NO_ERROR;
-    switch (increment.increment_case()) {
-        case increment.kTransaction: {
-            std::thread(&Replayer::doTransaction, this, increment.transaction(), event).detach();
-        } break;
-        case increment.kSurfaceCreation: {
-            std::thread(&Replayer::createSurfaceControl, this, increment.surface_creation(), event)
-                    .detach();
-        } break;
-        case increment.kBufferUpdate: {
-            std::lock_guard<std::mutex> lock1(mLayerLock);
-            std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
-
-            Dimensions dimensions(increment.buffer_update().w(), increment.buffer_update().h());
-            BufferEvent bufferEvent(event, dimensions);
-
-            auto layerId = increment.buffer_update().id();
-            if (mBufferQueueSchedulers.count(layerId) == 0) {
-                mBufferQueueSchedulers[layerId] = std::make_shared<BufferQueueScheduler>(
-                        mLayers[layerId], mColors[layerId], layerId);
-                mBufferQueueSchedulers[layerId]->addEvent(bufferEvent);
-
-                std::thread(&BufferQueueScheduler::startScheduling,
-                        mBufferQueueSchedulers[increment.buffer_update().id()].get())
-                        .detach();
-            } else {
-                auto bqs = mBufferQueueSchedulers[increment.buffer_update().id()];
-                bqs->addEvent(bufferEvent);
-            }
-        } break;
-        case increment.kVsyncEvent: {
-            std::thread(&Replayer::injectVSyncEvent, this, increment.vsync_event(), event).detach();
-        } break;
-        case increment.kDisplayCreation: {
-            std::thread(&Replayer::createDisplay, this, increment.display_creation(), event)
-                    .detach();
-        } break;
-        case increment.kDisplayDeletion: {
-            std::thread(&Replayer::deleteDisplay, this, increment.display_deletion(), event)
-                    .detach();
-        } break;
-        case increment.kPowerModeUpdate: {
-            std::thread(&Replayer::updatePowerMode, this, increment.power_mode_update(), event)
-                    .detach();
-        } break;
-        default:
-            ALOGE("Unknown Increment Type: %d", increment.increment_case());
-            status = BAD_VALUE;
-            break;
-    }
-
-    return status;
-}
-
-status_t Replayer::doTransaction(const Transaction& t, const std::shared_ptr<Event>& event) {
-    ALOGV("Started Transaction");
-
-    SurfaceComposerClient::Transaction liveTransaction;
-
-    status_t status = NO_ERROR;
-
-    status = doSurfaceTransaction(liveTransaction, t.surface_change());
-    doDisplayTransaction(liveTransaction, t.display_change());
-
-    if (t.animation()) {
-        liveTransaction.setAnimationTransaction();
-    }
-
-    event->readyToExecute();
-
-    liveTransaction.apply(t.synchronous());
-
-    ALOGV("Ended Transaction");
-
-    return status;
-}
-
-status_t Replayer::doSurfaceTransaction(
-        SurfaceComposerClient::Transaction& transaction,
-        const SurfaceChanges& surfaceChanges) {
-    status_t status = NO_ERROR;
-
-    for (const SurfaceChange& change : surfaceChanges) {
-        std::unique_lock<std::mutex> lock(mLayerLock);
-        if (mLayers[change.id()] == nullptr) {
-            mLayerCond.wait(lock, [&] { return (mLayers[change.id()] != nullptr); });
-        }
-
-        switch (change.SurfaceChange_case()) {
-            case SurfaceChange::SurfaceChangeCase::kPosition:
-                setPosition(transaction, change.id(), change.position());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kSize:
-                setSize(transaction, change.id(), change.size());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kAlpha:
-                setAlpha(transaction, change.id(), change.alpha());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kLayer:
-                setLayer(transaction, change.id(), change.layer());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kCrop:
-                setCrop(transaction, change.id(), change.crop());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kCornerRadius:
-                setCornerRadius(transaction, change.id(), change.corner_radius());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kMatrix:
-                setMatrix(transaction, change.id(), change.matrix());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kTransparentRegionHint:
-                setTransparentRegionHint(transaction, change.id(),
-                        change.transparent_region_hint());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kLayerStack:
-                setLayerStack(transaction, change.id(), change.layer_stack());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kHiddenFlag:
-                setHiddenFlag(transaction, change.id(), change.hidden_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kOpaqueFlag:
-                setOpaqueFlag(transaction, change.id(), change.opaque_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kSecureFlag:
-                setSecureFlag(transaction, change.id(), change.secure_flag());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kReparent:
-                setReparentChange(transaction, change.id(), change.reparent());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kRelativeParent:
-                setRelativeParentChange(transaction, change.id(), change.relative_parent());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kShadowRadius:
-                setShadowRadiusChange(transaction, change.id(), change.shadow_radius());
-                break;
-            case SurfaceChange::SurfaceChangeCase::kBlurRegions:
-                setBlurRegionsChange(transaction, change.id(), change.blur_regions());
-                break;
-            default:
-                status = 1;
-                break;
-        }
-
-        if (status != NO_ERROR) {
-            ALOGE("Unknown Transaction Code");
-            return status;
-        }
-    }
-    return status;
-}
-
-void Replayer::doDisplayTransaction(SurfaceComposerClient::Transaction& t,
-        const DisplayChanges& displayChanges) {
-    for (const DisplayChange& change : displayChanges) {
-        ALOGV("Doing display transaction");
-        std::unique_lock<std::mutex> lock(mDisplayLock);
-        if (mDisplays[change.id()] == nullptr) {
-            mDisplayCond.wait(lock, [&] { return (mDisplays[change.id()] != nullptr); });
-        }
-
-        switch (change.DisplayChange_case()) {
-            case DisplayChange::DisplayChangeCase::kSurface:
-                setDisplaySurface(t, change.id(), change.surface());
-                break;
-            case DisplayChange::DisplayChangeCase::kLayerStack:
-                setDisplayLayerStack(t, change.id(), change.layer_stack());
-                break;
-            case DisplayChange::DisplayChangeCase::kSize:
-                setDisplaySize(t, change.id(), change.size());
-                break;
-            case DisplayChange::DisplayChangeCase::kProjection:
-                setDisplayProjection(t, change.id(), change.projection());
-                break;
-            default:
-                break;
-        }
-    }
-}
-
-void Replayer::setPosition(SurfaceComposerClient::Transaction& t,
-        layer_id id, const PositionChange& pc) {
-    ALOGV("Layer %d: Setting Position -- x=%f, y=%f", id, pc.x(), pc.y());
-    t.setPosition(mLayers[id], pc.x(), pc.y());
-}
-
-void Replayer::setSize(SurfaceComposerClient::Transaction& t,
-        layer_id id, const SizeChange& sc) {
-    ALOGV("Layer %d: Setting Size -- w=%u, h=%u", id, sc.w(), sc.h());
-}
-
-void Replayer::setLayer(SurfaceComposerClient::Transaction& t,
-        layer_id id, const LayerChange& lc) {
-    ALOGV("Layer %d: Setting Layer -- layer=%d", id, lc.layer());
-    t.setLayer(mLayers[id], lc.layer());
-}
-
-void Replayer::setAlpha(SurfaceComposerClient::Transaction& t,
-        layer_id id, const AlphaChange& ac) {
-    ALOGV("Layer %d: Setting Alpha -- alpha=%f", id, ac.alpha());
-    t.setAlpha(mLayers[id], ac.alpha());
-}
-
-void Replayer::setCrop(SurfaceComposerClient::Transaction& t,
-        layer_id id, const CropChange& cc) {
-    ALOGV("Layer %d: Setting Crop -- left=%d, top=%d, right=%d, bottom=%d", id,
-            cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
-            cc.rectangle().bottom());
-
-    Rect r = Rect(cc.rectangle().left(), cc.rectangle().top(), cc.rectangle().right(),
-            cc.rectangle().bottom());
-    t.setCrop(mLayers[id], r);
-}
-
-void Replayer::setCornerRadius(SurfaceComposerClient::Transaction& t,
-        layer_id id, const CornerRadiusChange& cc) {
-    ALOGV("Layer %d: Setting Corner Radius -- cornerRadius=%d", id, cc.corner_radius());
-
-    t.setCornerRadius(mLayers[id], cc.corner_radius());
-}
-
-void Replayer::setBackgroundBlurRadius(SurfaceComposerClient::Transaction& t,
-        layer_id id, const BackgroundBlurRadiusChange& cc) {
-    ALOGV("Layer %d: Setting Background Blur Radius -- backgroundBlurRadius=%d", id,
-        cc.background_blur_radius());
-
-    t.setBackgroundBlurRadius(mLayers[id], cc.background_blur_radius());
-}
-
-void Replayer::setMatrix(SurfaceComposerClient::Transaction& t,
-        layer_id id, const MatrixChange& mc) {
-    ALOGV("Layer %d: Setting Matrix -- dsdx=%f, dtdx=%f, dsdy=%f, dtdy=%f", id, mc.dsdx(),
-            mc.dtdx(), mc.dsdy(), mc.dtdy());
-    t.setMatrix(mLayers[id], mc.dsdx(), mc.dtdx(), mc.dsdy(), mc.dtdy());
-}
-
-void Replayer::setTransparentRegionHint(SurfaceComposerClient::Transaction& t,
-        layer_id id, const TransparentRegionHintChange& trhc) {
-    ALOGV("Setting Transparent Region Hint");
-    Region re = Region();
-
-    for (const auto& r : trhc.region()) {
-        Rect rect = Rect(r.left(), r.top(), r.right(), r.bottom());
-        re.merge(rect);
-    }
-
-    t.setTransparentRegionHint(mLayers[id], re);
-}
-
-void Replayer::setLayerStack(SurfaceComposerClient::Transaction& t,
-        layer_id id, const LayerStackChange& lsc) {
-    ALOGV("Layer %d: Setting LayerStack -- layer_stack=%d", id, lsc.layer_stack());
-    t.setLayerStack(mLayers[id], ui::LayerStack::fromValue(lsc.layer_stack()));
-}
-
-void Replayer::setHiddenFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const HiddenFlagChange& hfc) {
-    ALOGV("Layer %d: Setting Hidden Flag -- hidden_flag=%d", id, hfc.hidden_flag());
-    layer_id flag = hfc.hidden_flag() ? layer_state_t::eLayerHidden : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerHidden);
-}
-
-void Replayer::setOpaqueFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const OpaqueFlagChange& ofc) {
-    ALOGV("Layer %d: Setting Opaque Flag -- opaque_flag=%d", id, ofc.opaque_flag());
-    layer_id flag = ofc.opaque_flag() ? layer_state_t::eLayerOpaque : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerOpaque);
-}
-
-void Replayer::setSecureFlag(SurfaceComposerClient::Transaction& t,
-        layer_id id, const SecureFlagChange& sfc) {
-    ALOGV("Layer %d: Setting Secure Flag -- secure_flag=%d", id, sfc.secure_flag());
-    layer_id flag = sfc.secure_flag() ? layer_state_t::eLayerSecure : 0;
-
-    t.setFlags(mLayers[id], flag, layer_state_t::eLayerSecure);
-}
-
-void Replayer::setDisplaySurface(SurfaceComposerClient::Transaction& t,
-        display_id id, const DispSurfaceChange& /*dsc*/) {
-    sp<IGraphicBufferProducer> outProducer;
-    sp<IGraphicBufferConsumer> outConsumer;
-    BufferQueue::createBufferQueue(&outProducer, &outConsumer);
-
-    t.setDisplaySurface(mDisplays[id], outProducer);
-}
-
-void Replayer::setDisplayLayerStack(SurfaceComposerClient::Transaction& t,
-        display_id id, const LayerStackChange& lsc) {
-    t.setDisplayLayerStack(mDisplays[id], ui::LayerStack::fromValue(lsc.layer_stack()));
-}
-
-void Replayer::setDisplaySize(SurfaceComposerClient::Transaction& t,
-        display_id id, const SizeChange& sc) {
-    t.setDisplaySize(mDisplays[id], sc.w(), sc.h());
-}
-
-void Replayer::setDisplayProjection(SurfaceComposerClient::Transaction& t,
-        display_id id, const ProjectionChange& pc) {
-    Rect viewport = Rect(pc.viewport().left(), pc.viewport().top(), pc.viewport().right(),
-            pc.viewport().bottom());
-    Rect frame = Rect(pc.frame().left(), pc.frame().top(), pc.frame().right(), pc.frame().bottom());
-
-    t.setDisplayProjection(mDisplays[id], ui::toRotation(pc.orientation()), viewport, frame);
-}
-
-status_t Replayer::createSurfaceControl(
-        const SurfaceCreation& create, const std::shared_ptr<Event>& event) {
-    event->readyToExecute();
-
-    ALOGV("Creating Surface Control: ID: %d", create.id());
-    sp<SurfaceControl> surfaceControl = mComposerClient->createSurface(
-            String8(create.name().c_str()), create.w(), create.h(), PIXEL_FORMAT_RGBA_8888, 0);
-
-    if (surfaceControl == nullptr) {
-        ALOGE("CreateSurfaceControl: unable to create surface control");
-        return BAD_VALUE;
-    }
-
-    std::lock_guard<std::mutex> lock1(mLayerLock);
-    auto& layer = mLayers[create.id()];
-    layer = surfaceControl;
-
-    mColors[create.id()] = HSV(rand() % 360, 1, 1);
-
-    mLayerCond.notify_all();
-
-    std::lock_guard<std::mutex> lock2(mBufferQueueSchedulerLock);
-    if (mBufferQueueSchedulers.count(create.id()) != 0) {
-        mBufferQueueSchedulers[create.id()]->setSurfaceControl(
-                mLayers[create.id()], mColors[create.id()]);
-    }
-
-    return NO_ERROR;
-}
-
-status_t Replayer::injectVSyncEvent(
-        const VSyncEvent& vSyncEvent, const std::shared_ptr<Event>& event) {
-    ALOGV("Injecting VSync Event");
-
-    event->readyToExecute();
-
-    SurfaceComposerClient::injectVSync(vSyncEvent.when());
-
-    return NO_ERROR;
-}
-
-void Replayer::createDisplay(const DisplayCreation& create, const std::shared_ptr<Event>& event) {
-    ALOGV("Creating display");
-    event->readyToExecute();
-
-    std::lock_guard<std::mutex> lock(mDisplayLock);
-    sp<IBinder> display = SurfaceComposerClient::createDisplay(
-            String8(create.name().c_str()), create.is_secure());
-    mDisplays[create.id()] = display;
-
-    mDisplayCond.notify_all();
-
-    ALOGV("Done creating display");
-}
-
-void Replayer::deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr<Event>& event) {
-    ALOGV("Delete display");
-    event->readyToExecute();
-
-    std::lock_guard<std::mutex> lock(mDisplayLock);
-    SurfaceComposerClient::destroyDisplay(mDisplays[delete_.id()]);
-    mDisplays.erase(delete_.id());
-}
-
-void Replayer::updatePowerMode(const PowerModeUpdate& pmu, const std::shared_ptr<Event>& event) {
-    ALOGV("Updating power mode");
-    event->readyToExecute();
-    SurfaceComposerClient::setDisplayPowerMode(mDisplays[pmu.id()], pmu.mode());
-}
-
-void Replayer::waitUntilTimestamp(int64_t timestamp) {
-    ALOGV("Waiting for %lld nanoseconds...", static_cast<int64_t>(timestamp - mCurrentTime));
-    std::this_thread::sleep_for(std::chrono::nanoseconds(timestamp - mCurrentTime));
-}
-
-status_t Replayer::loadSurfaceComposerClient() {
-    mComposerClient = new SurfaceComposerClient;
-    return mComposerClient->initCheck();
-}
-
-void Replayer::setReparentChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const ReparentChange& c) {
-    sp<SurfaceControl> newSurfaceControl = nullptr;
-    if (mLayers.count(c.parent_id()) != 0 && mLayers[c.parent_id()] != nullptr) {
-        newSurfaceControl = mLayers[c.parent_id()];
-    }
-    t.reparent(mLayers[id], newSurfaceControl);
-}
-
-void Replayer::setRelativeParentChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const RelativeParentChange& c) {
-    if (mLayers.count(c.relative_parent_id()) == 0 || mLayers[c.relative_parent_id()] == nullptr) {
-        ALOGE("Layer %d not found in set relative parent transaction", c.relative_parent_id());
-        return;
-    }
-    t.setRelativeLayer(mLayers[id], mLayers[c.relative_parent_id()], c.z());
-}
-
-void Replayer::setShadowRadiusChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const ShadowRadiusChange& c) {
-    t.setShadowRadius(mLayers[id], c.radius());
-}
-
-void Replayer::setBlurRegionsChange(SurfaceComposerClient::Transaction& t,
-        layer_id id, const BlurRegionsChange& c) {
-    std::vector<BlurRegion> regions;
-    for(size_t i=0; i < c.blur_regions_size(); i++) {
-        auto protoRegion = c.blur_regions(i);
-        regions.push_back(BlurRegion{
-            .blurRadius = protoRegion.blur_radius(),
-            .alpha = protoRegion.alpha(),
-            .cornerRadiusTL = protoRegion.corner_radius_tl(),
-            .cornerRadiusTR = protoRegion.corner_radius_tr(),
-            .cornerRadiusBL = protoRegion.corner_radius_bl(),
-            .cornerRadiusBR = protoRegion.corner_radius_br(),
-            .left = protoRegion.left(),
-            .top = protoRegion.top(),
-            .right = protoRegion.right(),
-            .bottom = protoRegion.bottom()
-        });
-    }
-    t.setBlurRegions(mLayers[id], regions);
-}
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index ab09de1..64ef827 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -287,6 +287,12 @@
 }
 
 prebuilt_etc {
+    name: "android.hardware.telephony.data.prebuilt.xml",
+    src: "android.hardware.telephony.data.xml",
+    defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
     name: "android.hardware.telephony.gsm.prebuilt.xml",
     src: "android.hardware.telephony.gsm.xml",
     defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.bluetooth_le.channel_sounding.xml b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
new file mode 100644
index 0000000..f0ee5d9
--- /dev/null
+++ b/data/etc/android.hardware.bluetooth_le.channel_sounding.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<!-- Adds the feature indicating support for the BLE Channel sounding -->
+<permissions>
+    <feature name="android.hardware.bluetooth_le.channel_sounding" />
+</permissions>
diff --git a/data/etc/android.hardware.telephony.satellite.xml b/data/etc/android.hardware.telephony.satellite.xml
new file mode 100644
index 0000000..945e720
--- /dev/null
+++ b/data/etc/android.hardware.telephony.satellite.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- This is the standard set of features for devices to support Telephony Satellite API. -->
+<permissions>
+    <feature name="android.hardware.telephony" />
+    <feature name="android.hardware.telephony.satellite" />
+</permissions>
\ No newline at end of file
diff --git a/data/etc/android.software.contextualsearch.xml b/data/etc/android.software.contextualsearch.xml
deleted file mode 100644
index 4564ac8..0000000
--- a/data/etc/android.software.contextualsearch.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<!-- Feature for devices supporting contextual search helper. -->
-<permissions>
-    <feature name="android.software.contextualsearch" />
-</permissions>
diff --git a/data/etc/go_handheld_core_hardware.xml b/data/etc/go_handheld_core_hardware.xml
index 8df7fdb..a092842 100644
--- a/data/etc/go_handheld_core_hardware.xml
+++ b/data/etc/go_handheld_core_hardware.xml
@@ -51,6 +51,9 @@
     <!-- Feature to specify if the device supports adding device admins. -->
     <feature name="android.software.device_admin" />
 
+    <!-- Feature to specify if the device support managed users. -->
+    <feature name="android.software.managed_users" />
+
     <!-- Devices with all optimizations required to support VR Mode and
          pass all CDD requirements for this feature may include
          android.hardware.vr.high_performance -->
diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml
index 39772ae..f593eda 100644
--- a/data/etc/input/motion_predictor_config.xml
+++ b/data/etc/input/motion_predictor_config.xml
@@ -31,5 +31,15 @@
        the UX issue mentioned above.
   -->
   <distance-noise-floor>0.2</distance-noise-floor>
+  <!-- The low and high jerk thresholds for prediction pruning.
+
+    The jerk thresholds are based on normalized dt = 1 calculations.
+  -->
+  <low-jerk>1.5</low-jerk>
+  <high-jerk>2.0</high-jerk>
+
+  <!-- The alpha in the first-order IIR filter for jerk smoothing. An alpha
+       of 1 results in no smoothing.-->
+  <jerk-alpha>0.25</jerk-alpha>
 </motion-predictor>
 
diff --git a/include/android/OWNERS b/include/android/OWNERS
index c6cf7ad..9dfa279 100644
--- a/include/android/OWNERS
+++ b/include/android/OWNERS
@@ -1 +1,13 @@
 per-file input.h,keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
+
+# Window manager
+per-file surface_control_input_receiver.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file input_transfer_token.h = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+
+# CoGS
+per-file *luts* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+
+# ADPF
+per-file performance_hint.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file system_health.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file thermal.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index bec3283..2622a01 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -318,7 +318,7 @@
 
 /**
  * Gets the token used by the platform to identify the frame timeline at the given \c index.
- * q
+ *
  * Available since API level 33.
  *
  * \param index index of a frame timeline, in \f( [0, FrameTimelinesLength) \f). See
diff --git a/include/android/input.h b/include/android/input.h
index fec56f0..ee98d7a 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -1002,7 +1002,6 @@
  * Keyboard types.
  *
  * Refer to the documentation on android.view.InputDevice for more details.
- * Note: When adding a new keyboard type here InputDeviceInfo::setKeyboardType needs to be updated.
  */
 enum {
     /** none */
diff --git a/include/android/input_transfer_token_jni.h b/include/android/input_transfer_token_jni.h
new file mode 100644
index 0000000..92fe9b6
--- /dev/null
+++ b/include/android/input_transfer_token_jni.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2024 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file input_transfer_token_jni.h
+ */
+
+#pragma once
+
+#include <sys/cdefs.h>
+#include <jni.h>
+
+__BEGIN_DECLS
+struct AInputTransferToken;
+
+/**
+ * AInputTransferToken can be used to request focus on or to transfer touch gesture to and from
+ * an embedded SurfaceControl
+ */
+typedef struct AInputTransferToken AInputTransferToken;
+
+/**
+ * Return the AInputTransferToken wrapped by a Java InputTransferToken object. This must be released
+ * using AInputTransferToken_release
+ *
+ * inputTransferTokenObj must be a non-null instance of android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+AInputTransferToken* _Nonnull AInputTransferToken_fromJava(JNIEnv* _Nonnull env,
+        jobject _Nonnull inputTransferTokenObj) __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * Return the Java InputTransferToken object that wraps AInputTransferToken
+ *
+ * aInputTransferToken must be non null and the returned value is an object of instance
+ * android.window.InputTransferToken.
+ *
+ * Available since API level 35.
+ */
+jobject _Nonnull AInputTransferToken_toJava(JNIEnv* _Nonnull env,
+        const AInputTransferToken* _Nonnull aInputTransferToken) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Removes a reference that was previously acquired in native.
+ *
+ * Available since API level 35.
+ */
+void AInputTransferToken_release(AInputTransferToken* _Nullable aInputTransferToken)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
+/** @} */
diff --git a/include/android/looper.h b/include/android/looper.h
index d80a366..409db84 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -90,20 +90,23 @@
     ALOOPER_POLL_WAKE = -1,
 
     /**
-     * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * One or more callbacks were executed.
+     * Result from ALooper_pollOnce():
+     * One or more callbacks were executed. The poll may also have been
+     * explicitly woken by ALooper_wake().
      */
     ALOOPER_POLL_CALLBACK = -2,
 
     /**
      * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * The timeout expired.
+     * The timeout expired. The poll may also have been explicitly woken by
+     * ALooper_wake().
      */
     ALOOPER_POLL_TIMEOUT = -3,
 
     /**
      * Result from ALooper_pollOnce() and ALooper_pollAll():
-     * An error occurred.
+     * An error occurred. The poll may also have been explicitly woken by
+     * ALooper_wake().
      */
     ALOOPER_POLL_ERROR = -4,
 };
@@ -182,10 +185,13 @@
  * If the timeout is zero, returns immediately without blocking.
  * If the timeout is negative, waits indefinitely until an event appears.
  *
+ * **All return values may also imply ALOOPER_POLL_WAKE.** If you call this in a
+ * loop, you must treat all return values as if they also indicated
+ * ALOOPER_POLL_WAKE.
+ *
  * Returns ALOOPER_POLL_WAKE if the poll was awoken using ALooper_wake() before
  * the timeout expired and no callbacks were invoked and no other file
- * descriptors were ready. **All return values may also imply
- * ALOOPER_POLL_WAKE.**
+ * descriptors were ready.
  *
  * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked. The poll
  * may also have been explicitly woken by ALooper_wake.
@@ -214,15 +220,15 @@
  * data has been consumed or a file descriptor is available with no callback.
  * This function will never return ALOOPER_POLL_CALLBACK.
  *
- * This API cannot be used safely, but a safe alternative exists (see below). As
- * such, new builds will not be able to call this API and must migrate to the
- * safe API. Binary compatibility is preserved to support already-compiled apps.
+ * This API will not reliably respond to ALooper_wake. As such, this API is
+ * hidden and callers should migrate to ALooper_pollOnce. Binary compatibility
+ * is preserved to support already-compiled apps.
  *
- * \bug ALooper_pollAll will not wake in response to ALooper_wake calls if it
+ * \bug ALooper_pollAll() will not wake in response to ALooper_wake() calls if it
  * also handles another event at the same time.
  *
- * \deprecated Calls to ALooper_pollAll should be replaced with
- * ALooper_pollOnce. If you call ALooper_pollOnce in a loop, you *must* treat
+ * \deprecated Calls to ALooper_pollAll() should be replaced with
+ * ALooper_pollOnce(). If you call ALooper_pollOnce() in a loop, you *must* treat
  * all return values as if they also indicate ALOOPER_POLL_WAKE.
  */
 int ALooper_pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData)
@@ -235,6 +241,8 @@
  *
  * This method can be called on any thread.
  * This method returns immediately.
+ *
+ * \bug ALooper_pollAll() will not reliably wake in response to ALooper_wake().
  */
 void ALooper_wake(ALooper* looper);
 
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 62d0423..3486e9b 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -52,7 +52,6 @@
  *   - DO NOT CHANGE THE LAYOUT OR SIZE OF STRUCTURES
  */
 
-#include <android/api-level.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <unistd.h>
@@ -85,7 +84,6 @@
 
 /**
  * An opaque type representing a handle to a performance hint manager.
- * It must be released after use.
  *
  * To use:<ul>
  *    <li>Obtain the performance hint manager instance by calling
@@ -115,6 +113,9 @@
  * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
  * cycle to report the actual time taken to complete to the system.
  *
+ * Note, methods of {@link APerformanceHintSession_*} are not thread safe so callers must
+ * ensure thread safety.
+ *
  * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
  */
 typedef struct APerformanceHintSession APerformanceHintSession;
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 665d9c6..bf9acb3 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -145,6 +145,9 @@
  * Buffers which are replaced or removed from the scene in the transaction invoking
  * this callback may be reused after this point.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_OnBufferRelease to listen
+ * to when a buffer is ready to be reused.
+ *
  * \param context Optional context provided by the client that is passed into
  * the callback.
  *
@@ -157,8 +160,7 @@
  * Available since API level 29.
  */
 typedef void (*ASurfaceTransaction_OnComplete)(void* _Null_unspecified context,
-                                               ASurfaceTransactionStats* _Nonnull stats)
-        __INTRODUCED_IN(29);
+                                               ASurfaceTransactionStats* _Nonnull stats);
 
 /**
  * The ASurfaceTransaction_OnCommit callback is invoked when transaction is applied and the updates
@@ -186,8 +188,36 @@
  * Available since API level 31.
  */
 typedef void (*ASurfaceTransaction_OnCommit)(void* _Null_unspecified context,
-                                             ASurfaceTransactionStats* _Nonnull stats)
-        __INTRODUCED_IN(31);
+                                             ASurfaceTransactionStats* _Nonnull stats);
+
+/**
+ * The ASurfaceTransaction_OnBufferRelease callback is invoked when a buffer that was passed in
+ * ASurfaceTransaction_setBuffer is ready to be reused.
+ *
+ * This callback is guaranteed to be invoked if ASurfaceTransaction_setBuffer is called with a non
+ * null buffer. If the buffer in the transaction is replaced via another call to
+ * ASurfaceTransaction_setBuffer, the callback will be invoked immediately. Otherwise the callback
+ * will be invoked before the ASurfaceTransaction_OnComplete callback after the buffer was
+ * presented.
+ *
+ * If this callback is set, caller should not release the buffer using the
+ * ASurfaceTransaction_OnComplete.
+ *
+ * \param context Optional context provided by the client that is passed into the callback.
+ *
+ * \param release_fence_fd Returns the fence file descriptor used to signal the release of buffer
+ * associated with this callback. If this fence is valid (>=0), the buffer has not yet been released
+ * and the fence will signal when the buffer has been released. If the fence is -1 , the buffer is
+ * already released. The recipient of the callback takes ownership of the fence fd and is
+ * responsible for closing it.
+ *
+ * THREADING
+ * The callback can be invoked on any thread.
+ *
+ * Available since API level 36.
+ */
+typedef void (*ASurfaceTransaction_OnBufferRelease)(void* _Null_unspecified context,
+                                                    int release_fence_fd);
 
 /**
  * Returns the timestamp of when the frame was latched by the framework. Once a frame is
@@ -239,6 +269,10 @@
  * it is acquired. If no acquire_fence_fd was provided, this timestamp will be set to -1.
  *
  * Available since API level 29.
+ *
+ * @deprecated This may return SIGNAL_PENDING because the stats can arrive before the acquire
+ * fence has signaled, depending on internal timing differences. Therefore the caller should
+ * use the acquire fence passed in to setBuffer and query the signal time.
  */
 int64_t ASurfaceTransactionStats_getAcquireTime(
         ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
@@ -247,7 +281,7 @@
 /**
  * The returns the fence used to signal the release of the PREVIOUS buffer set on
  * this surface. If this fence is valid (>=0), the PREVIOUS buffer has not yet been released and the
- * fence will signal when the PREVIOUS buffer has been released. If the fence is -1 , the PREVIOUS
+ * fence will signal when the PREVIOUS buffer has been released. If the fence is -1, the PREVIOUS
  * buffer is already released. The recipient of the callback takes ownership of the
  * previousReleaseFenceFd and is responsible for closing it.
  *
@@ -349,6 +383,9 @@
  * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
  * as the surface control might be composited using the GPU.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_setBufferWithRelease to
+ * set a buffer and a callback which will be invoked when the buffer is ready to be reused.
+ *
  * Available since API level 29.
  */
 void ASurfaceTransaction_setBuffer(ASurfaceTransaction* _Nonnull transaction,
@@ -357,6 +394,29 @@
         __INTRODUCED_IN(29);
 
 /**
+ * Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
+ * acquire_fence_fd should be a file descriptor that is signaled when all pending work
+ * for the buffer is complete and the buffer can be safely read.
+ *
+ * The frameworks takes ownership of the \a acquire_fence_fd passed and is responsible
+ * for closing it.
+ *
+ * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
+ * as the surface control might be composited using the GPU.
+ *
+ * When the buffer is ready to be reused, the ASurfaceTransaction_OnBufferRelease
+ * callback will be invoked. If the buffer is null, the callback will not be invoked.
+ *
+ * Available since API level 36.
+ */
+void ASurfaceTransaction_setBufferWithRelease(ASurfaceTransaction* _Nonnull transaction,
+                                              ASurfaceControl* _Nonnull surface_control,
+                                              AHardwareBuffer* _Nonnull buffer,
+                                              int acquire_fence_fd, void* _Null_unspecified context,
+                                              ASurfaceTransaction_OnBufferRelease _Nonnull func)
+        __INTRODUCED_IN(36);
+
+/**
  * Updates the color for \a surface_control.  This will make the background color for the
  * ASurfaceControl visible in transparent regions of the surface.  Colors \a r, \a g,
  * and \a b must be within the range that is valid for \a dataspace.  \a dataspace and \a alpha
@@ -573,7 +633,7 @@
  * using this API for formats that encode an HDR/SDR ratio as part of generating the buffer.
  *
  * @param surface_control The layer whose extended range brightness is being specified
- * @param currentBufferRatio The current hdr/sdr ratio of the current buffer as represented as
+ * @param currentBufferRatio The current HDR/SDR ratio of the current buffer as represented as
  *                           peakHdrBrightnessInNits / targetSdrWhitePointInNits. For example if the
  *                           buffer was rendered with a target SDR whitepoint of 100nits and a max
  *                           display brightness of 200nits, this should be set to 2.0f.
@@ -587,7 +647,7 @@
  *
  *                           Must be finite && >= 1.0f
  *
- * @param desiredRatio The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits /
+ * @param desiredRatio The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
  *                     targetSdrWhitePointInNits. This can be used to communicate the max desired
  *                     brightness range. This is similar to the "max luminance" value in other
  *                     HDR metadata formats, but represented as a ratio of the target SDR whitepoint
@@ -620,13 +680,13 @@
                                                     __INTRODUCED_IN(__ANDROID_API_U__);
 
 /**
- * Sets the desired hdr headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
+ * Sets the desired HDR headroom for the layer. See: ASurfaceTransaction_setExtendedRangeBrightness,
  * prefer using this API for formats that conform to HDR standards like HLG or HDR10, that do not
  * communicate a HDR/SDR ratio as part of generating the buffer.
  *
- * @param surface_control The layer whose desired hdr headroom is being specified
+ * @param surface_control The layer whose desired HDR headroom is being specified
  *
- * @param desiredHeadroom The desired hdr/sdr ratio as represented as peakHdrBrightnessInNits /
+ * @param desiredHeadroom The desired HDR/SDR ratio as represented as peakHdrBrightnessInNits /
  *                        targetSdrWhitePointInNits. This can be used to communicate the max
  *                        desired brightness range of the panel. The system may not be able to, or
  *                        may choose not to, deliver the requested range.
diff --git a/include/android/surface_control_input_receiver.h b/include/android/surface_control_input_receiver.h
new file mode 100644
index 0000000..f0503f6
--- /dev/null
+++ b/include/android/surface_control_input_receiver.h
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+/**
+ * @addtogroup NativeActivity Native Activity
+ * @{
+ */
+/**
+ * @file surface_control_input_receiver.h
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <android/input.h>
+#include <android/surface_control.h>
+#include <android/input_transfer_token_jni.h>
+
+__BEGIN_DECLS
+
+/**
+ * The AInputReceiver_onMotionEvent callback is invoked when the registered input channel receives
+ * a motion event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param motionEvent The motion event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onMotionEvent)(void *_Null_unspecified context,
+                                             AInputEvent *_Nonnull motionEvent)
+                                            __INTRODUCED_IN(__ANDROID_API_V__);
+/**
+ * The AInputReceiver_onKeyEvent callback is invoked when the registered input channel receives
+ * a key event.
+ *
+ * \param context Optional context provided by the client that is passed when creating the
+ * AInputReceiverCallbacks.
+ *
+ * \param keyEvent The key event. This must be released with AInputEvent_release.
+ *
+ * Available since API level 35.
+ */
+typedef bool (*AInputReceiver_onKeyEvent)(void *_Null_unspecified context,
+                                          AInputEvent *_Nonnull keyEvent)
+                                          __INTRODUCED_IN(__ANDROID_API_V__);
+
+typedef struct AInputReceiverCallbacks AInputReceiverCallbacks;
+
+/**
+ * The InputReceiver that holds the reference to the registered input channel. This must be released
+ * using AInputReceiver_release
+ */
+typedef struct AInputReceiver AInputReceiver;
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive batched input event. For
+ * those events that are batched, the invocation will happen once per AChoreographer frame, and
+ * other input events will be delivered immediately.
+ *
+ * This is different from AInputReceiver_createUnbatchedInputReceiver in that the input events are
+ * received batched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aChoreographer         The AChoreographer used for batching. This should match the
+ *                               rendering AChoreographer.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createBatchedInputReceiver(AChoreographer* _Nonnull aChoreographer,
+                                        const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                        const ASurfaceControl* _Nonnull aSurfaceControl,
+                                        AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Registers an input receiver for an ASurfaceControl that will receive every input event.
+ * This is different from AInputReceiver_createBatchedInputReceiver in that the input events are
+ * received unbatched. The caller must invoke AInputReceiver_release to clean up the resources when
+ * no longer needing to use the input receiver.
+ *
+ * \param aLooper                The looper to use when invoking callbacks.
+ * \param hostInputTransferToken The host token to link the embedded. This is used to handle
+ *                               transferring touch gesture from host to embedded and for ANRs
+ *                               to ensure the host receives the ANR if any issues with
+ *                               touch on the embedded. This can be retrieved for the host window
+ *                               by calling AttachedSurfaceControl#getInputTransferToken()
+ * \param aSurfaceControl        The ASurfaceControl to register the InputChannel for
+ * \param aInputReceiverCallbacks The SurfaceControlInputReceiver that will receive the input events
+ *
+ * Returns the reference to AInputReceiver to clean up resources when done.
+ *
+ * Available since API level 35.
+ */
+AInputReceiver* _Nonnull
+AInputReceiver_createUnbatchedInputReceiver(ALooper* _Nonnull aLooper,
+                                         const AInputTransferToken* _Nonnull hostInputTransferToken,
+                                         const ASurfaceControl* _Nonnull aSurfaceControl,
+                                         AInputReceiverCallbacks* _Nonnull aInputReceiverCallbacks)
+                                         __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Returns the AInputTransferToken that can be used to transfer touch gesture to or from other
+ * windows. This InputTransferToken is associated with the SurfaceControl that registered an input
+ * receiver and can be used with the host token for things like transfer touch gesture via
+ * WindowManager#transferTouchGesture().
+ *
+ * This must be released with AInputTransferToken_release.
+ *
+ * \param aInputReceiver The inputReceiver object to retrieve the AInputTransferToken for.
+ *
+ * Available since API level 35.
+ */
+const AInputTransferToken *_Nonnull
+AInputReceiver_getInputTransferToken(AInputReceiver *_Nonnull aInputReceiver)
+        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Unregisters the input channel and deletes the AInputReceiver. This must be called on the same
+ * looper thread it was created with.
+ *
+ * \param aInputReceiver The inputReceiver object to release.
+ *
+ * Available since API level 35.
+ */
+void
+AInputReceiver_release(AInputReceiver *_Nullable aInputReceiver) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a AInputReceiverCallbacks object that is used when registering for an AInputReceiver.
+ * This must be released using AInputReceiverCallbacks_release
+ *
+ * \param context Optional context provided by the client that will be passed into the callbacks.
+ *
+ * Available since API level 35.
+ */
+AInputReceiverCallbacks* _Nonnull AInputReceiverCallbacks_create(void* _Nullable context)
+                                                        __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Releases the AInputReceiverCallbacks. This must be called on the same
+ * looper thread the AInputReceiver was created with. The receiver will not invoke any callbacks
+ * once it's been released.
+ *
+ * Available since API level 35
+ */
+void AInputReceiverCallbacks_release(AInputReceiverCallbacks* _Nullable callbacks)
+                                     __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onMotionEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The motion event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setMotionEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                AInputReceiver_onMotionEvent _Nonnull onMotionEvent)
+                                                __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets a AInputReceiver_onKeyEvent callback for an AInputReceiverCallbacks
+ *
+ * \param callbacks The callback object to set the motion event on.
+ * \param onMotionEvent The key event that will be invoked
+ *
+ * Available since API level 35.
+ */
+void AInputReceiverCallbacks_setKeyEventCallback(AInputReceiverCallbacks* _Nonnull callbacks,
+                                                 AInputReceiver_onKeyEvent _Nonnull onKeyEvent)
+                                                 __INTRODUCED_IN(__ANDROID_API_V__);
+
+__END_DECLS
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 7f9d2ed..1d7ad12 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -254,7 +254,7 @@
  * The headroom threshold is used to interpret the possible thermal throttling status based on
  * the headroom prediction. For example, if the headroom threshold for
  * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
- * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * (or `AThermal_getThermalHeadroom(10)=0.75`), one can expect that in 10 seconds the system
  * could be in lightly throttled state if the workload remains the same. The app can consider
  * taking actions according to the nearest throttling status the difference between the headroom and
  * the threshold.
@@ -262,10 +262,10 @@
  * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
  * sensors reporting different threshold values, the minimum threshold is taken to be conservative
  * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
- * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * of 0.75 (or `AThermal_getThermalHeadroom(0)`=0.75) exceeding the threshold of 0.7 above
  * will always come with lightly throttled state
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
- * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT`) but it can be lower
+ * (or `AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE`).
  * While it's always guaranteed that the device won't be throttled heavier than the unmet
  * threshold's state, so a real-time headroom of 0.75 will never come with
  * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
diff --git a/include/audiomanager/IAudioManager.h b/include/audiomanager/IAudioManager.h
index 769670e..0b7e16b 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -27,7 +27,7 @@
 namespace android {
 
 // ----------------------------------------------------------------------------
-
+// TODO(b/309532236) replace this class with AIDL generated parcelable
 class IAudioManager : public IInterface
 {
 public:
@@ -43,6 +43,7 @@
         RELEASE_RECORDER                      = IBinder::FIRST_CALL_TRANSACTION + 6,
         PLAYER_SESSION_ID                     = IBinder::FIRST_CALL_TRANSACTION + 7,
         PORT_EVENT                            = IBinder::FIRST_CALL_TRANSACTION + 8,
+        PERMISSION_UPDATE_BARRIER             = IBinder::FIRST_CALL_TRANSACTION + 9,
     };
 
     DECLARE_META_INTERFACE(AudioManager)
@@ -63,6 +64,7 @@
     /*oneway*/ virtual status_t playerSessionId(audio_unique_id_t piid, audio_session_t sessionId) = 0;
     /*oneway*/ virtual status_t portEvent(audio_port_handle_t portId, player_state_t event,
                 const std::unique_ptr<os::PersistableBundle>& extras) = 0;
+    virtual status_t permissionUpdateBarrier() = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/audiomanager/OWNERS b/include/audiomanager/OWNERS
index 2bd527c..58257ba 100644
--- a/include/audiomanager/OWNERS
+++ b/include/audiomanager/OWNERS
@@ -1,2 +1,3 @@
+atneya@google.com
 elaurent@google.com
 jmtrivi@google.com
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h
index c0f6768..68826bb 100644
--- a/include/ftl/algorithm.h
+++ b/include/ftl/algorithm.h
@@ -24,6 +24,18 @@
 
 namespace android::ftl {
 
+// Determines if a container contains a value. This is a simplified version of the C++23
+// std::ranges::contains function.
+//
+//   const ftl::StaticVector vector = {1, 2, 3};
+//   assert(ftl::contains(vector, 1));
+//
+// TODO: Remove in C++23.
+template <typename Container, typename Value>
+auto contains(const Container& container, const Value& value) -> bool {
+  return std::find(container.begin(), container.end(), value) != container.end();
+}
+
 // Adapter for std::find_if that converts the return value from iterator to optional.
 //
 //   const ftl::StaticVector vector = {"upside"sv, "down"sv, "cake"sv};
diff --git a/include/ftl/concat.h b/include/ftl/concat.h
index e0774d3..7680bed 100644
--- a/include/ftl/concat.h
+++ b/include/ftl/concat.h
@@ -57,7 +57,7 @@
 template <std::size_t N>
 struct Concat<N> {
   static constexpr std::size_t max_size() { return N; }
-  constexpr std::size_t size() const { return end_ - buffer_; }
+  constexpr std::size_t size() const { return static_cast<std::size_t>(end_ - buffer_); }
 
   constexpr const char* c_str() const { return buffer_; }
 
@@ -68,6 +68,8 @@
 
  protected:
   constexpr Concat() : end_(buffer_) {}
+  constexpr Concat(const Concat&) = delete;
+
   constexpr void append() { *end_ = '\0'; }
 
   char buffer_[N + 1];
diff --git a/include/ftl/details/hash.h b/include/ftl/details/hash.h
new file mode 100644
index 0000000..230ae51
--- /dev/null
+++ b/include/ftl/details/hash.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2024 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 <cinttypes>
+#include <cstring>
+
+namespace android::ftl::details {
+
+// Based on CityHash64 v1.0.1 (http://code.google.com/p/cityhash/), but slightly
+// modernized and trimmed for cases with bounded lengths.
+
+template <typename T = std::uint64_t>
+inline T read_unaligned(const void* ptr) {
+  T v;
+  std::memcpy(&v, ptr, sizeof(T));
+  return v;
+}
+
+template <bool NonZeroShift = false>
+constexpr std::uint64_t rotate(std::uint64_t v, std::uint8_t shift) {
+  if constexpr (!NonZeroShift) {
+    if (shift == 0) return v;
+  }
+  return (v >> shift) | (v << (64 - shift));
+}
+
+constexpr std::uint64_t shift_mix(std::uint64_t v) {
+  return v ^ (v >> 47);
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+constexpr std::uint64_t hash_length_16(std::uint64_t u, std::uint64_t v) {
+  constexpr std::uint64_t kPrime = 0x9ddfea08eb382d69ull;
+  auto a = (u ^ v) * kPrime;
+  a ^= (a >> 47);
+  auto b = (v ^ a) * kPrime;
+  b ^= (b >> 47);
+  b *= kPrime;
+  return b;
+}
+
+constexpr std::uint64_t kPrime0 = 0xc3a5c85c97cb3127ull;
+constexpr std::uint64_t kPrime1 = 0xb492b66fbe98f273ull;
+constexpr std::uint64_t kPrime2 = 0x9ae16a3b2f90404full;
+constexpr std::uint64_t kPrime3 = 0xc949d7c7509e6557ull;
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_0_to_16(const char* str, std::uint64_t length) {
+  if (length > 8) {
+    const auto a = read_unaligned(str);
+    const auto b = read_unaligned(str + length - 8);
+    return hash_length_16(a, rotate<true>(b + length, static_cast<std::uint8_t>(length))) ^ b;
+  }
+  if (length >= 4) {
+    const auto a = read_unaligned<std::uint32_t>(str);
+    const auto b = read_unaligned<std::uint32_t>(str + length - 4);
+    return hash_length_16(length + (a << 3), b);
+  }
+  if (length > 0) {
+    const auto a = static_cast<unsigned char>(str[0]);
+    const auto b = static_cast<unsigned char>(str[length >> 1]);
+    const auto c = static_cast<unsigned char>(str[length - 1]);
+    const auto y = static_cast<std::uint32_t>(a) + (static_cast<std::uint32_t>(b) << 8);
+    const auto z = static_cast<std::uint32_t>(length) + (static_cast<std::uint32_t>(c) << 2);
+    return shift_mix(y * kPrime2 ^ z * kPrime3) * kPrime2;
+  }
+  return kPrime2;
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_17_to_32(const char* str, std::uint64_t length) {
+  const auto a = read_unaligned(str) * kPrime1;
+  const auto b = read_unaligned(str + 8);
+  const auto c = read_unaligned(str + length - 8) * kPrime2;
+  const auto d = read_unaligned(str + length - 16) * kPrime0;
+  return hash_length_16(rotate(a - b, 43) + rotate(c, 30) + d,
+                        a + rotate(b ^ kPrime3, 20) - c + length);
+}
+
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+inline std::uint64_t hash_length_33_to_64(const char* str, std::uint64_t length) {
+  auto z = read_unaligned(str + 24);
+  auto a = read_unaligned(str) + (length + read_unaligned(str + length - 16)) * kPrime0;
+  auto b = rotate(a + z, 52);
+  auto c = rotate(a, 37);
+
+  a += read_unaligned(str + 8);
+  c += rotate(a, 7);
+  a += read_unaligned(str + 16);
+
+  const auto vf = a + z;
+  const auto vs = b + rotate(a, 31) + c;
+
+  a = read_unaligned(str + 16) + read_unaligned(str + length - 32);
+  z += read_unaligned(str + length - 8);
+  b = rotate(a + z, 52);
+  c = rotate(a, 37);
+  a += read_unaligned(str + length - 24);
+  c += rotate(a, 7);
+  a += read_unaligned(str + length - 16);
+
+  const auto wf = a + z;
+  const auto ws = b + rotate(a, 31) + c;
+  const auto r = shift_mix((vf + ws) * kPrime2 + (wf + vs) * kPrime0);
+  return shift_mix(r * kPrime0 + vs) * kPrime2;
+}
+
+}  // namespace android::ftl::details
diff --git a/include/ftl/expected.h b/include/ftl/expected.h
index 12b6102..7e765c5 100644
--- a/include/ftl/expected.h
+++ b/include/ftl/expected.h
@@ -18,9 +18,87 @@
 
 #include <android-base/expected.h>
 #include <ftl/optional.h>
+#include <ftl/unit.h>
 
 #include <utility>
 
+// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short), FTL_TRY
+// unwraps T out of R, or bails out of the enclosing function F if R has an error E. The return type
+// of F must be R, since FTL_TRY propagates R in the error case. As a special case, ftl::Unit may be
+// used as the error E to allow FTL_TRY expressions when F returns `void`.
+//
+// The non-standard syntax requires `-Wno-gnu-statement-expression-from-macro-expansion` to compile.
+// The UnitToVoid conversion allows the macro to be used for early exit from a function that returns
+// `void`.
+//
+// Example usage:
+//
+//   using StringExp = ftl::Expected<std::string, std::errc>;
+//
+//   StringExp repeat(StringExp exp) {
+//     const std::string str = FTL_TRY(exp);
+//     return StringExp(str + str);
+//   }
+//
+//   assert(StringExp("haha"s) == repeat(StringExp("ha"s)));
+//   assert(repeat(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
+//     return e == std::errc::bad_message;
+//   }));
+//
+//
+// FTL_TRY may be used in void-returning functions by using ftl::Unit as the error type:
+//
+//   void uppercase(char& c, ftl::Optional<char> opt) {
+//     c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
+//   }
+//
+//   char c = '?';
+//   uppercase(c, std::nullopt);
+//   assert(c == '?');
+//
+//   uppercase(c, 'a');
+//   assert(c == 'A');
+//
+#define FTL_TRY(expr)                                                     \
+  ({                                                                      \
+    auto exp_ = (expr);                                                   \
+    if (!exp_.has_value()) {                                              \
+      using E = decltype(exp_)::error_type;                               \
+      return android::ftl::details::UnitToVoid<E>::from(std::move(exp_)); \
+    }                                                                     \
+    exp_.value();                                                         \
+  })
+
+// Given an expression `expr` that evaluates to an ftl::Expected<T, E> result (R for short),
+// FTL_EXPECT unwraps T out of R, or bails out of the enclosing function F if R has an error E.
+// While FTL_TRY bails out with R, FTL_EXPECT bails out with E, which is useful when F does not
+// need to propagate R because T is not relevant to the caller.
+//
+// Example usage:
+//
+//   using StringExp = ftl::Expected<std::string, std::errc>;
+//
+//   std::errc repeat(StringExp exp, std::string& out) {
+//     const std::string str = FTL_EXPECT(exp);
+//     out = str + str;
+//     return std::errc::operation_in_progress;
+//   }
+//
+//   std::string str;
+//   assert(std::errc::operation_in_progress == repeat(StringExp("ha"s), str));
+//   assert("haha"s == str);
+//   assert(std::errc::bad_message == repeat(ftl::Unexpected(std::errc::bad_message), str));
+//   assert("haha"s == str);
+//
+#define FTL_EXPECT(expr)              \
+  ({                                  \
+    auto exp_ = (expr);               \
+    if (!exp_.has_value()) {          \
+      return std::move(exp_.error()); \
+    }                                 \
+    exp_.value();                     \
+  })
+
 namespace android::ftl {
 
 // Superset of base::expected<T, E> with monadic operations.
diff --git a/include/ftl/fake_guard.h b/include/ftl/fake_guard.h
index e601251..0bf2870 100644
--- a/include/ftl/fake_guard.h
+++ b/include/ftl/fake_guard.h
@@ -76,12 +76,8 @@
   FTL_ATTRIBUTE(release_capability(mutex))
 #endif
 
-// The parentheses around `expr` are needed to deduce an lvalue or rvalue reference.
-#define FTL_FAKE_GUARD2(mutex, expr)            \
-  [&]() -> decltype(auto) {                     \
-    const android::ftl::FakeGuard guard(mutex); \
-    return (expr);                              \
-  }()
+#define FTL_FAKE_GUARD2(mutex, expr) \
+    (android::ftl::FakeGuard(mutex), expr)
 
 #define FTL_MAKE_FAKE_GUARD(arg1, arg2, guard, ...) guard
 
diff --git a/include/ftl/function.h b/include/ftl/function.h
index 3538ca4..bda5b75 100644
--- a/include/ftl/function.h
+++ b/include/ftl/function.h
@@ -123,7 +123,7 @@
 //   // Create a typedef to give a more meaningful name and bound the size.
 //   using MyFunction = ftl::Function<int(std::string_view), 2>;
 //   int* ptr = nullptr;
-//   auto f1 = MyFunction::make_function(
+//   auto f1 = MyFunction::make(
 //       [cls = &cls, ptr](std::string_view sv) {
 //           return cls->on_string(ptr, sv);
 //       });
diff --git a/include/ftl/hash.h b/include/ftl/hash.h
new file mode 100644
index 0000000..29a6f71
--- /dev/null
+++ b/include/ftl/hash.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 <cinttypes>
+#include <optional>
+#include <string_view>
+
+#include <ftl/details/hash.h>
+
+namespace android::ftl {
+
+// Non-cryptographic hash function (namely CityHash64) for strings with at most 64 characters.
+// Unlike std::hash, which returns std::size_t and is only required to produce the same result
+// for the same input within a single execution of a program, this hash is stable.
+inline std::optional<std::uint64_t> stable_hash(std::string_view view) {
+  const auto length = view.length();
+  if (length <= 16) {
+    return details::hash_length_0_to_16(view.data(), length);
+  }
+  if (length <= 32) {
+    return details::hash_length_17_to_32(view.data(), length);
+  }
+  if (length <= 64) {
+    return details::hash_length_33_to_64(view.data(), length);
+  }
+  return {};
+}
+
+}  // namespace android::ftl
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
index 35d09d7..4a5d8bf 100644
--- a/include/ftl/non_null.h
+++ b/include/ftl/non_null.h
@@ -68,26 +68,28 @@
   constexpr NonNull(const NonNull&) = default;
   constexpr NonNull& operator=(const NonNull&) = default;
 
-  constexpr const Pointer& get() const { return pointer_; }
-  constexpr explicit operator const Pointer&() const { return get(); }
+  [[nodiscard]] constexpr const Pointer& get() const { return pointer_; }
+  [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); }
 
   // Move operations. These break the invariant, so care must be taken to avoid subsequent access.
 
   constexpr NonNull(NonNull&&) = default;
   constexpr NonNull& operator=(NonNull&&) = default;
 
-  constexpr Pointer take() && { return std::move(pointer_); }
-  constexpr explicit operator Pointer() && { return take(); }
+  [[nodiscard]] constexpr Pointer take() && { return std::move(pointer_); }
+  [[nodiscard]] constexpr explicit operator Pointer() && { return take(); }
 
   // Dereferencing.
-  constexpr decltype(auto) operator*() const { return *get(); }
-  constexpr decltype(auto) operator->() const { return get(); }
+  [[nodiscard]] constexpr decltype(auto) operator*() const { return *get(); }
+  [[nodiscard]] constexpr decltype(auto) operator->() const { return get(); }
+
+  [[nodiscard]] constexpr explicit operator bool() const { return !(pointer_ == nullptr); }
 
   // Private constructor for ftl::as_non_null. Excluded from candidate constructors for conversions
   // through the passkey idiom, for clear compilation errors.
   template <typename P>
   constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) {
-    if (!pointer_) std::abort();
+    if (pointer_ == nullptr) std::abort();
   }
 
  private:
@@ -98,11 +100,13 @@
 };
 
 template <typename P>
-constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
+[[nodiscard]] constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
   using Passkey = typename NonNull<std::decay_t<P>>::Passkey;
   return {Passkey{}, std::forward<P>(pointer)};
 }
 
+// NonNull<P> <=> NonNull<Q>
+
 template <typename P, typename Q>
 constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
   return lhs.get() == rhs.get();
@@ -113,4 +117,96 @@
   return !operator==(lhs, rhs);
 }
 
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+  return lhs.get() > rhs.get();
+}
+
+// NonNull<P> <=> Q
+
+template <typename P, typename Q>
+constexpr bool operator==(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() == rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() != rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() < rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() <= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() >= rhs;
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const NonNull<P>& lhs, const Q& rhs) {
+  return lhs.get() > rhs;
+}
+
+// P <=> NonNull<Q>
+
+template <typename P, typename Q>
+constexpr bool operator==(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs == rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs != rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs < rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator<=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs <= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>=(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs >= rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator>(const P& lhs, const NonNull<Q>& rhs) {
+  return lhs > rhs.get();
+}
+
 }  // namespace android::ftl
+
+// Specialize std::hash for ftl::NonNull<T>
+template <typename P>
+struct std::hash<android::ftl::NonNull<P>> {
+  std::size_t operator()(const android::ftl::NonNull<P>& ptr) const {
+    return std::hash<P>()(ptr.get());
+  }
+};
diff --git a/include/ftl/optional.h b/include/ftl/optional.h
index 94d8e3d..e245d88 100644
--- a/include/ftl/optional.h
+++ b/include/ftl/optional.h
@@ -20,13 +20,14 @@
 #include <optional>
 #include <utility>
 
+#include <android-base/expected.h>
 #include <ftl/details/optional.h>
 
 namespace android::ftl {
 
 // Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8.
 //
-// TODO: Remove in C++23.
+// TODO: Remove standard APIs in C++23.
 //
 template <typename T>
 struct Optional final : std::optional<T> {
@@ -109,6 +110,13 @@
     return std::forward<F>(f)();
   }
 
+  // Maps this Optional<T> to expected<T, E> where nullopt becomes E.
+  template <typename E>
+  constexpr auto ok_or(E&& e) && -> base::expected<T, E> {
+    if (has_value()) return std::move(value());
+    return base::unexpected(std::forward<E>(e));
+  }
+
   // Delete new for this class. Its base doesn't have a virtual destructor, and
   // if it got deleted via base class pointer, it would cause undefined
   // behavior. There's not a good reason to allocate this object on the heap
diff --git a/include/ftl/small_vector.h b/include/ftl/small_vector.h
index 43e9fac..3d5d52e 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -234,7 +234,7 @@
   }
 
   // Extracts the elements as std::vector.
-  std::vector<T> promote() && {
+  std::vector<std::remove_const_t<T>> promote() && {
     if (dynamic()) {
       return std::get<Dynamic>(std::move(vector_)).promote();
     } else {
@@ -290,11 +290,11 @@
 class SmallVector<T, 0> final : details::ArrayTraits<T>,
                                 details::ArrayComparators<SmallVector>,
                                 details::ArrayIterators<SmallVector<T, 0>, T>,
-                                std::vector<T> {
+                                std::vector<std::remove_const_t<T>> {
   using details::ArrayTraits<T>::replace_at;
 
   using Iter = details::ArrayIterators<SmallVector, T>;
-  using Impl = std::vector<T>;
+  using Impl = std::vector<std::remove_const_t<T>>;
 
   friend Iter;
 
@@ -394,12 +394,12 @@
     pop_back();
   }
 
-  std::vector<T> promote() && { return std::move(*this); }
+  std::vector<std::remove_const_t<T>> promote() && { return std::move(*this); }
 
  private:
   template <typename U, std::size_t M>
   static Impl convert(SmallVector<U, M>&& other) {
-    if constexpr (std::is_constructible_v<Impl, std::vector<U>&&>) {
+    if constexpr (std::is_constructible_v<Impl, std::vector<std::remove_const_t<U>>&&>) {
       return std::move(other).promote();
     } else {
       SmallVector vector(other.size());
diff --git a/include/ftl/unit.h b/include/ftl/unit.h
index e38230b..62549a3 100644
--- a/include/ftl/unit.h
+++ b/include/ftl/unit.h
@@ -58,4 +58,22 @@
   return {std::forward<F>(f)};
 }
 
+namespace details {
+
+// Identity function for all T except Unit, which maps to void.
+template <typename T>
+struct UnitToVoid {
+  template <typename U>
+  static auto from(U&& value) {
+    return value;
+  }
+};
+
+template <>
+struct UnitToVoid<Unit> {
+  template <typename U>
+  static void from(U&&) {}
+};
+
+}  // namespace details
 }  // namespace android::ftl
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index b0eceef..56294dd 100644
--- a/include/input/DisplayViewport.h
+++ b/include/input/DisplayViewport.h
@@ -19,7 +19,6 @@
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
 #include <ftl/string.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <ui/Rotation.h>
 
@@ -47,7 +46,7 @@
  * See com.android.server.display.DisplayViewport.
  */
 struct DisplayViewport {
-    int32_t displayId; // -1 if invalid
+    ui::LogicalDisplayId displayId;
     ui::Rotation orientation;
     int32_t logicalLeft;
     int32_t logicalTop;
@@ -67,7 +66,7 @@
     ViewportType type;
 
     DisplayViewport()
-          : displayId(ADISPLAY_ID_NONE),
+          : displayId(ui::LogicalDisplayId::INVALID),
             orientation(ui::ROTATION_0),
             logicalLeft(0),
             logicalTop(0),
@@ -99,12 +98,10 @@
         return !(*this == other);
     }
 
-    inline bool isValid() const {
-        return displayId >= 0;
-    }
+    inline bool isValid() const { return displayId.isValid(); }
 
     void setNonDisplayViewport(int32_t width, int32_t height) {
-        displayId = ADISPLAY_ID_NONE;
+        displayId = ui::LogicalDisplayId::INVALID;
         orientation = ui::ROTATION_0;
         logicalLeft = 0;
         logicalTop = 0;
@@ -123,12 +120,13 @@
     }
 
     std::string toString() const {
-        return StringPrintf("Viewport %s: displayId=%d, uniqueId=%s, port=%s, orientation=%d, "
+        return StringPrintf("Viewport %s: displayId=%s, uniqueId=%s, port=%s, orientation=%d, "
                             "logicalFrame=[%d, %d, %d, %d], "
                             "physicalFrame=[%d, %d, %d, %d], "
                             "deviceSize=[%d, %d], "
                             "isActive=[%d]",
-                            ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(),
+                            ftl::enum_string(type).c_str(), displayId.toString().c_str(),
+                            uniqueId.c_str(),
                             physicalPort ? ftl::to_string(*physicalPort).c_str() : "<none>",
                             static_cast<int>(orientation), logicalLeft, logicalTop, logicalRight,
                             logicalBottom, physicalLeft, physicalTop, physicalRight, physicalBottom,
diff --git a/include/input/Input.h b/include/input/Input.h
index a84dcfc..a8684bd 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -25,9 +25,12 @@
 #include <android/input.h>
 #ifdef __linux__
 #include <android/os/IInputConstants.h>
+#include <android/os/MotionEventFlag.h>
 #endif
+#include <android/os/PointerIconType.h>
 #include <math.h>
 #include <stdint.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -39,13 +42,11 @@
  * Additional private constants not defined in ndk/ui/input.h.
  */
 enum {
-#ifdef __linux__
+
     /* This event was generated or modified by accessibility service. */
     AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
-#else
-    AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
-#endif
+            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
     /* Signifies that the key is being predispatched */
     AKEY_EVENT_FLAG_PREDISPATCH = 0x20000000,
 
@@ -53,11 +54,11 @@
     AKEY_EVENT_FLAG_START_TRACKING = 0x40000000,
 
     /* Key event is inconsistent with previously sent key events. */
-    AKEY_EVENT_FLAG_TAINTED = 0x80000000,
+    AKEY_EVENT_FLAG_TAINTED = android::os::IInputConstants::INPUT_EVENT_FLAG_TAINTED,
 };
 
 enum {
-
+    // AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED is defined in include/android/input.h
     /**
      * This flag indicates that the window that received this motion event is partly
      * or wholly obscured by another visible window above it.  This flag is set to true
@@ -68,13 +69,18 @@
      * to drop the suspect touches or to take additional precautions to confirm the user's
      * actual intent.
      */
-    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
+    AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED =
+            static_cast<int32_t>(android::os::MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED),
+
+    AMOTION_EVENT_FLAG_HOVER_EXIT_PENDING =
+            static_cast<int32_t>(android::os::MotionEventFlag::HOVER_EXIT_PENDING),
 
     /**
      * This flag indicates that the event has been generated by a gesture generator. It
      * provides a hint to the GestureDetector to not apply any touch slop.
      */
-    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8,
+    AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE =
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_GENERATED_GESTURE),
 
     /**
      * This flag indicates that the event will not cause a focus change if it is directed to an
@@ -82,20 +88,32 @@
      * gestures to allow the user to direct gestures to an unfocused window without bringing it
      * into focus.
      */
-    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,
+    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE =
+            static_cast<int32_t>(android::os::MotionEventFlag::NO_FOCUS_CHANGE),
 
-#if defined(__linux__)
     /**
      * This event was generated or modified by accessibility service.
      */
     AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT =
-            android::os::IInputConstants::INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT, // 0x800,
-#else
-    AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800,
-#endif
+            static_cast<int32_t>(android::os::MotionEventFlag::IS_ACCESSIBILITY_EVENT),
+
+    AMOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS =
+            static_cast<int32_t>(android::os::MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS),
 
     /* Motion event is inconsistent with previously sent motion events. */
-    AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
+    AMOTION_EVENT_FLAG_TAINTED = static_cast<int32_t>(android::os::MotionEventFlag::TAINTED),
+
+    /** Private flag, not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+            static_cast<int32_t>(android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION),
+
+    /** Private flag, not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = static_cast<int32_t>(
+            android::os::MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION),
+
+    /** Mask for all private flags that are not used in Java. */
+    AMOTION_EVENT_PRIVATE_FLAG_MASK = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
 };
 
 /**
@@ -115,9 +133,10 @@
 /**
  * This flag indicates that the point up event has been canceled.
  * Typically this is used for palm event when the user has accidental touches.
- * TODO: Adjust flag to public api
+ * TODO(b/338143308): Add this to NDK
  */
-constexpr int32_t AMOTION_EVENT_FLAG_CANCELED = 0x20;
+constexpr int32_t AMOTION_EVENT_FLAG_CANCELED =
+        android::os::IInputConstants::INPUT_EVENT_FLAG_CANCELED;
 
 enum {
     /*
@@ -177,6 +196,13 @@
 #define MAX_POINTER_ID 31
 
 /*
+ * Number of high resolution scroll units for one detent (scroll wheel click), as defined in
+ * evdev. This is relevant when an input device is emitting REL_WHEEL_HI_RES or REL_HWHEEL_HI_RES
+ * events.
+ */
+constexpr int32_t kEvdevHighResScrollUnitsPerDetent = 120;
+
+/*
  * Declare a concrete type for the NDK's input event forward declaration.
  */
 struct AInputEvent {
@@ -194,9 +220,7 @@
 
 namespace android {
 
-#ifdef __linux__
 class Parcel;
-#endif
 
 /*
  * Apply the given transform to the point without applying any translation/offset.
@@ -207,8 +231,12 @@
  * Transform an angle on the x-y plane. An angle of 0 radians corresponds to "north" or
  * pointing upwards in the negative Y direction, a positive angle points towards the right, and a
  * negative angle points towards the left.
+ *
+ * If the angle represents a direction that needs to be preserved, set isDirectional to true to get
+ * an output range of [-pi, pi]. If the angle's direction does not need to be preserved, set
+ * isDirectional to false to get an output range of [-pi/2, pi/2].
  */
-float transformAngle(const ui::Transform& transform, float angleRadians);
+float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional);
 
 /**
  * The type of the InputEvent.
@@ -223,6 +251,8 @@
     TOUCH_MODE = AINPUT_EVENT_TYPE_TOUCH_MODE,
     ftl_first = KEY,
     ftl_last = TOUCH_MODE,
+    // Used by LatencyTracker fuzzer
+    kMaxValue = ftl_last
 };
 
 std::string inputEventSourceToString(int32_t source);
@@ -256,6 +286,16 @@
     ftl_last = VIRTUAL,
 };
 
+/**
+ * The keyboard type. This should have 1:1 correspondence with the values of anonymous enum
+ * defined in input.h
+ */
+enum class KeyboardType {
+    NONE = AINPUT_KEYBOARD_TYPE_NONE,
+    NON_ALPHABETIC = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+    ALPHABETIC = AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+};
+
 bool isStylusToolType(ToolType toolType);
 
 struct PointerProperties;
@@ -302,12 +342,8 @@
 
     POLICY_FLAG_RAW_MASK = 0x0000ffff,
 
-#ifdef __linux__
     POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY =
             android::os::IInputConstants::POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY,
-#else
-    POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000,
-#endif
 
     /* These flags are set by the input dispatcher. */
 
@@ -464,8 +500,6 @@
     // axes, however the window scaling will not.
     void scale(float globalScale, float windowXScale, float windowYScale);
 
-    void transform(const ui::Transform& transform);
-
     inline float getX() const {
         return getAxisValue(AMOTION_EVENT_AXIS_X);
     }
@@ -476,10 +510,8 @@
 
     vec2 getXYValue() const { return vec2(getX(), getY()); }
 
-#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
-#endif
 
     bool operator==(const PointerCoords& other) const;
     inline bool operator!=(const PointerCoords& other) const {
@@ -538,9 +570,9 @@
 
     inline void setSource(uint32_t source) { mSource = source; }
 
-    inline int32_t getDisplayId() const { return mDisplayId; }
+    inline ui::LogicalDisplayId getDisplayId() const { return mDisplayId; }
 
-    inline void setDisplayId(int32_t displayId) { mDisplayId = displayId; }
+    inline void setDisplayId(ui::LogicalDisplayId displayId) { mDisplayId = displayId; }
 
     inline std::array<uint8_t, 32> getHmac() const { return mHmac; }
 
@@ -549,7 +581,7 @@
     bool operator==(const InputEvent&) const = default;
 
 protected:
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac);
 
     void initialize(const InputEvent& from);
@@ -557,7 +589,7 @@
     int32_t mId;
     DeviceId mDeviceId;
     uint32_t mSource;
-    int32_t mDisplayId;
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::INVALID};
     std::array<uint8_t, 32> mHmac;
 };
 
@@ -593,7 +625,7 @@
     static const char* getLabel(int32_t keyCode);
     static std::optional<int> getKeyCodeFromLabel(const char* label);
 
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode,
                     int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime,
                     nsecs_t eventTime);
@@ -662,10 +694,6 @@
 
     inline void setActionButton(int32_t button) { mActionButton = button; }
 
-    inline float getXOffset() const { return mTransform.tx(); }
-
-    inline float getYOffset() const { return mTransform.ty(); }
-
     inline const ui::Transform& getTransform() const { return mTransform; }
 
     std::optional<ui::Rotation> getSurfaceRotation() const;
@@ -859,7 +887,7 @@
 
     ssize_t findPointerIndex(int32_t pointerId) const;
 
-    void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
+    void initialize(int32_t id, DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId,
                     std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
@@ -870,12 +898,30 @@
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
 
-    void addSample(
-            nsecs_t eventTime,
-            const PointerCoords* pointerCoords);
+    // Initialize this event by keeping only the pointers from "other" that are in splitPointerIds.
+    void splitFrom(const MotionEvent& other, std::bitset<MAX_POINTER_ID + 1> splitPointerIds,
+                   int32_t newEventId);
+
+    void addSample(nsecs_t eventTime, const PointerCoords* pointerCoords, int32_t eventId);
 
     void offsetLocation(float xOffset, float yOffset);
 
+    /**
+     * Get the X offset of this motion event relative to the origin of the raw coordinate space.
+     *
+     * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical
+     * display space) to adjust for the absolute position of the containing windows and views.
+     */
+    float getRawXOffset() const;
+
+    /**
+     * Get the Y offset of this motion event relative to the origin of the raw coordinate space.
+     *
+     * In practice, this is the delta that was added to the raw screen coordinates (i.e. in logical
+     * display space) to adjust for the absolute position of the containing windows and views.
+     */
+    float getRawYOffset() const;
+
     void scale(float globalScaleFactor);
 
     // Set 3x3 perspective matrix transformation.
@@ -886,10 +932,8 @@
     // Matrix is in row-major form and compatible with SkMatrix.
     void applyTransform(const std::array<float, 9>& matrix);
 
-#ifdef __linux__
     status_t readFromParcel(Parcel* parcel);
     status_t writeToParcel(Parcel* parcel) const;
-#endif
 
     static bool isTouchEvent(uint32_t source, int32_t action);
     inline bool isTouchEvent() const {
@@ -910,15 +954,22 @@
 
     static std::string actionToString(int32_t action);
 
+    static std::tuple<int32_t /*action*/, std::vector<PointerProperties>,
+                      std::vector<PointerCoords>>
+    split(int32_t action, int32_t flags, int32_t historySize, const std::vector<PointerProperties>&,
+          const std::vector<PointerCoords>&, std::bitset<MAX_POINTER_ID + 1> splitPointerIds);
+
     // MotionEvent will transform various axes in different ways, based on the source. For
     // example, the x and y axes will not have any offsets/translations applied if it comes from a
     // relative mouse device (since SOURCE_RELATIVE_MOUSE is a non-pointer source). These methods
     // are used to apply these transformations for different axes.
     static vec2 calculateTransformedXY(uint32_t source, const ui::Transform&, const vec2& xy);
-    static float calculateTransformedAxisValue(int32_t axis, uint32_t source, const ui::Transform&,
-                                               const PointerCoords&);
-    static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&,
-                                                    const PointerCoords&);
+    static float calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags,
+                                               const ui::Transform&, const PointerCoords&);
+    static void calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source,
+                                                  int32_t flags, const ui::Transform&);
+    static PointerCoords calculateTransformedCoords(uint32_t source, int32_t flags,
+                                                    const ui::Transform&, const PointerCoords&);
     // The rounding precision for transformed motion events.
     static constexpr float ROUNDING_PRECISION = 0.001f;
 
@@ -1043,7 +1094,7 @@
     DeviceId deviceId;
     nsecs_t eventTimeNanos;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
 };
 
 /**
@@ -1171,15 +1222,17 @@
  */
 struct PointerCaptureRequest {
 public:
-    inline PointerCaptureRequest() : enable(false), seq(0) {}
-    inline PointerCaptureRequest(bool enable, uint32_t seq) : enable(enable), seq(seq) {}
+    inline PointerCaptureRequest() : window(), seq(0) {}
+    inline PointerCaptureRequest(sp<IBinder> window, uint32_t seq) : window(window), seq(seq) {}
     inline bool operator==(const PointerCaptureRequest& other) const {
-        return enable == other.enable && seq == other.seq;
+        return window == other.window && seq == other.seq;
     }
-    explicit inline operator bool() const { return enable; }
+    inline bool isEnable() const { return window != nullptr; }
 
-    // True iff this is a request to enable Pointer Capture.
-    bool enable;
+    // The requesting window.
+    // If the request is to enable the capture, this is the input token of the window that requested
+    // pointer capture. Otherwise, this is nullptr.
+    sp<IBinder> window;
 
     // The sequence number for the request.
     uint32_t seq;
@@ -1190,43 +1243,41 @@
  *
  * Due to backwards compatibility and public api constraints, this is a duplicate (but type safe)
  * definition of PointerIcon.java.
- *
- * TODO(b/235023317) move this definition to an aidl and statically assign to the below java public
- * api values.
- *
- * WARNING: Keep these definitions in sync with
- * frameworks/base/core/java/android/view/PointerIcon.java
  */
 enum class PointerIconStyle : int32_t {
-    TYPE_CUSTOM = -1,
-    TYPE_NULL = 0,
-    TYPE_NOT_SPECIFIED = 1,
-    TYPE_ARROW = 1000,
-    TYPE_CONTEXT_MENU = 1001,
-    TYPE_HAND = 1002,
-    TYPE_HELP = 1003,
-    TYPE_WAIT = 1004,
-    TYPE_CELL = 1006,
-    TYPE_CROSSHAIR = 1007,
-    TYPE_TEXT = 1008,
-    TYPE_VERTICAL_TEXT = 1009,
-    TYPE_ALIAS = 1010,
-    TYPE_COPY = 1011,
-    TYPE_NO_DROP = 1012,
-    TYPE_ALL_SCROLL = 1013,
-    TYPE_HORIZONTAL_DOUBLE_ARROW = 1014,
-    TYPE_VERTICAL_DOUBLE_ARROW = 1015,
-    TYPE_TOP_RIGHT_DOUBLE_ARROW = 1016,
-    TYPE_TOP_LEFT_DOUBLE_ARROW = 1017,
-    TYPE_ZOOM_IN = 1018,
-    TYPE_ZOOM_OUT = 1019,
-    TYPE_GRAB = 1020,
-    TYPE_GRABBING = 1021,
-    TYPE_HANDWRITING = 1022,
+    TYPE_CUSTOM = static_cast<int32_t>(::android::os::PointerIconType::CUSTOM),
+    TYPE_NULL = static_cast<int32_t>(::android::os::PointerIconType::TYPE_NULL),
+    TYPE_NOT_SPECIFIED = static_cast<int32_t>(::android::os::PointerIconType::NOT_SPECIFIED),
+    TYPE_ARROW = static_cast<int32_t>(::android::os::PointerIconType::ARROW),
+    TYPE_CONTEXT_MENU = static_cast<int32_t>(::android::os::PointerIconType::CONTEXT_MENU),
+    TYPE_HAND = static_cast<int32_t>(::android::os::PointerIconType::HAND),
+    TYPE_HELP = static_cast<int32_t>(::android::os::PointerIconType::HELP),
+    TYPE_WAIT = static_cast<int32_t>(::android::os::PointerIconType::WAIT),
+    TYPE_CELL = static_cast<int32_t>(::android::os::PointerIconType::CELL),
+    TYPE_CROSSHAIR = static_cast<int32_t>(::android::os::PointerIconType::CROSSHAIR),
+    TYPE_TEXT = static_cast<int32_t>(::android::os::PointerIconType::TEXT),
+    TYPE_VERTICAL_TEXT = static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_TEXT),
+    TYPE_ALIAS = static_cast<int32_t>(::android::os::PointerIconType::ALIAS),
+    TYPE_COPY = static_cast<int32_t>(::android::os::PointerIconType::COPY),
+    TYPE_NO_DROP = static_cast<int32_t>(::android::os::PointerIconType::NO_DROP),
+    TYPE_ALL_SCROLL = static_cast<int32_t>(::android::os::PointerIconType::ALL_SCROLL),
+    TYPE_HORIZONTAL_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::HORIZONTAL_DOUBLE_ARROW),
+    TYPE_VERTICAL_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::VERTICAL_DOUBLE_ARROW),
+    TYPE_TOP_RIGHT_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::TOP_RIGHT_DOUBLE_ARROW),
+    TYPE_TOP_LEFT_DOUBLE_ARROW =
+            static_cast<int32_t>(::android::os::PointerIconType::TOP_LEFT_DOUBLE_ARROW),
+    TYPE_ZOOM_IN = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_IN),
+    TYPE_ZOOM_OUT = static_cast<int32_t>(::android::os::PointerIconType::ZOOM_OUT),
+    TYPE_GRAB = static_cast<int32_t>(::android::os::PointerIconType::GRAB),
+    TYPE_GRABBING = static_cast<int32_t>(::android::os::PointerIconType::GRABBING),
+    TYPE_HANDWRITING = static_cast<int32_t>(::android::os::PointerIconType::HANDWRITING),
 
-    TYPE_SPOT_HOVER = 2000,
-    TYPE_SPOT_TOUCH = 2001,
-    TYPE_SPOT_ANCHOR = 2002,
+    TYPE_SPOT_HOVER = static_cast<int32_t>(::android::os::PointerIconType::SPOT_HOVER),
+    TYPE_SPOT_TOUCH = static_cast<int32_t>(::android::os::PointerIconType::SPOT_TOUCH),
+    TYPE_SPOT_ANCHOR = static_cast<int32_t>(::android::os::PointerIconType::SPOT_ANCHOR),
 };
 
 } // namespace android
diff --git a/include/input/InputConsumer.h b/include/input/InputConsumer.h
new file mode 100644
index 0000000..611478c
--- /dev/null
+++ b/include/input/InputConsumer.h
@@ -0,0 +1,254 @@
+/*
+ * Copyright 2024 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
+
+/*
+ * Native input transport.
+ *
+ * The InputConsumer is used by the application to receive events from the input dispatcher.
+ */
+
+#include "InputTransport.h"
+
+namespace android {
+
+/*
+ * Consumes input events from an input channel.
+ */
+class InputConsumer {
+public:
+    /* Create a consumer associated with an input channel. */
+    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
+    /* Create a consumer associated with an input channel, override resampling system property */
+    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
+                           bool enableTouchResampling);
+
+    /* Destroys the consumer and releases its input channel. */
+    ~InputConsumer();
+
+    /* Gets the underlying input channel. */
+    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
+
+    /* Consumes an input event from the input channel and copies its contents into
+     * an InputEvent object created using the specified factory.
+     *
+     * Tries to combine a series of move events into larger batches whenever possible.
+     *
+     * If consumeBatches is false, then defers consuming pending batched events if it
+     * is possible for additional samples to be added to them later.  Call hasPendingBatch()
+     * to determine whether a pending batch is available to be consumed.
+     *
+     * If consumeBatches is true, then events are still batched but they are consumed
+     * immediately as soon as the input channel is exhausted.
+     *
+     * The frameTime parameter specifies the time when the current display frame started
+     * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
+     *
+     * The returned sequence number is never 0 unless the operation failed.
+     *
+     * Returns OK on success.
+     * Returns WOULD_BLOCK if there is no event present.
+     * Returns DEAD_OBJECT if the channel's peer has been closed.
+     * Returns NO_MEMORY if the event could not be created.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
+                     uint32_t* outSeq, InputEvent** outEvent);
+
+    /* Sends a finished signal to the publisher to inform it that the message
+     * with the specified sequence number has finished being process and whether
+     * the message was handled by the consumer.
+     *
+     * Returns OK on success.
+     * Returns BAD_VALUE if seq is 0.
+     * Other errors probably indicate that the channel is broken.
+     */
+    status_t sendFinishedSignal(uint32_t seq, bool handled);
+
+    status_t sendTimeline(int32_t inputEventId,
+                          std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+    /* Returns true if there is a pending batch.
+     *
+     * Should be called after calling consume() with consumeBatches == false to determine
+     * whether consume() should be called again later on with consumeBatches == true.
+     */
+    bool hasPendingBatch() const;
+
+    /* Returns the source of first pending batch if exist.
+     *
+     * Should be called after calling consume() with consumeBatches == false to determine
+     * whether consume() should be called again later on with consumeBatches == true.
+     */
+    int32_t getPendingBatchSource() const;
+
+    /* Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    std::string dump() const;
+
+private:
+    // True if touch resampling is enabled.
+    const bool mResampleTouch;
+
+    std::shared_ptr<InputChannel> mChannel;
+
+    // TODO(b/311142655): delete this temporary tracing after the ANR bug is fixed
+    const std::string mProcessingTraceTag;
+    const std::string mLifetimeTraceTag;
+    const int32_t mLifetimeTraceCookie;
+
+    // The current input message.
+    InputMessage mMsg;
+
+    // True if mMsg contains a valid input message that was deferred from the previous
+    // call to consume and that still needs to be handled.
+    bool mMsgDeferred;
+
+    // Batched motion events per device and source.
+    struct Batch {
+        std::vector<InputMessage> samples;
+    };
+    std::vector<Batch> mBatches;
+
+    // Touch state per device and source, only for sources of class pointer.
+    struct History {
+        nsecs_t eventTime;
+        BitSet32 idBits;
+        int32_t idToIndex[MAX_POINTER_ID + 1];
+        PointerCoords pointers[MAX_POINTERS];
+
+        void initializeFrom(const InputMessage& msg) {
+            eventTime = msg.body.motion.eventTime;
+            idBits.clear();
+            for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                uint32_t id = msg.body.motion.pointers[i].properties.id;
+                idBits.markBit(id);
+                idToIndex[id] = i;
+                pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
+            }
+        }
+
+        void initializeFrom(const History& other) {
+            eventTime = other.eventTime;
+            idBits = other.idBits; // temporary copy
+            for (size_t i = 0; i < other.idBits.count(); i++) {
+                uint32_t id = idBits.clearFirstMarkedBit();
+                int32_t index = other.idToIndex[id];
+                idToIndex[id] = index;
+                pointers[index].copyFrom(other.pointers[index]);
+            }
+            idBits = other.idBits; // final copy
+        }
+
+        const PointerCoords& getPointerById(uint32_t id) const { return pointers[idToIndex[id]]; }
+
+        bool hasPointerId(uint32_t id) const { return idBits.hasBit(id); }
+    };
+    struct TouchState {
+        int32_t deviceId;
+        int32_t source;
+        size_t historyCurrent;
+        size_t historySize;
+        History history[2];
+        History lastResample;
+
+        void initialize(int32_t incomingDeviceId, int32_t incomingSource) {
+            deviceId = incomingDeviceId;
+            source = incomingSource;
+            historyCurrent = 0;
+            historySize = 0;
+            lastResample.eventTime = 0;
+            lastResample.idBits.clear();
+        }
+
+        void addHistory(const InputMessage& msg) {
+            historyCurrent ^= 1;
+            if (historySize < 2) {
+                historySize += 1;
+            }
+            history[historyCurrent].initializeFrom(msg);
+        }
+
+        const History* getHistory(size_t index) const {
+            return &history[(historyCurrent + index) & 1];
+        }
+
+        bool recentCoordinatesAreIdentical(uint32_t id) const {
+            // Return true if the two most recently received "raw" coordinates are identical
+            if (historySize < 2) {
+                return false;
+            }
+            if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
+                return false;
+            }
+            float currentX = getHistory(0)->getPointerById(id).getX();
+            float currentY = getHistory(0)->getPointerById(id).getY();
+            float previousX = getHistory(1)->getPointerById(id).getX();
+            float previousY = getHistory(1)->getPointerById(id).getY();
+            if (currentX == previousX && currentY == previousY) {
+                return true;
+            }
+            return false;
+        }
+    };
+    std::vector<TouchState> mTouchStates;
+
+    // Chain of batched sequence numbers.  When multiple input messages are combined into
+    // a batch, we append a record here that associates the last sequence number in the
+    // batch with the previous one.  When the finished signal is sent, we traverse the
+    // chain to individually finish all input messages that were part of the batch.
+    struct SeqChain {
+        uint32_t seq;   // sequence number of batched input message
+        uint32_t chain; // sequence number of previous batched input message
+    };
+    std::vector<SeqChain> mSeqChains;
+
+    // The time at which each event with the sequence number 'seq' was consumed.
+    // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+    // This collection is populated when the event is received, and the entries are erased when the
+    // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+    // will be raised for that connection, and no further events will be posted to that channel.
+    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+
+    status_t consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime, uint32_t* outSeq,
+                          InputEvent** outEvent);
+    status_t consumeSamples(InputEventFactoryInterface* factory, Batch& batch, size_t count,
+                            uint32_t* outSeq, InputEvent** outEvent);
+
+    void updateTouchState(InputMessage& msg);
+    void resampleTouchState(nsecs_t frameTime, MotionEvent* event, const InputMessage* next);
+
+    ssize_t findBatch(int32_t deviceId, int32_t source) const;
+    ssize_t findTouchState(int32_t deviceId, int32_t source) const;
+
+    nsecs_t getConsumeTime(uint32_t seq) const;
+    void popConsumeTime(uint32_t seq);
+    status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
+
+    static void rewriteMessage(TouchState& state, InputMessage& msg);
+    static bool canAddSample(const Batch& batch, const InputMessage* msg);
+    static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
+
+    static bool isTouchResamplingEnabled();
+};
+
+} // namespace android
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
new file mode 100644
index 0000000..c98b9cf
--- /dev/null
+++ b/include/input/InputConsumerNoResampling.h
@@ -0,0 +1,253 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <map>
+#include <memory>
+#include <optional>
+
+#include <input/Input.h>
+#include <input/InputTransport.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
+
+namespace android {
+
+/**
+ * An interface to receive batched input events. Even if you don't want batching, you still have to
+ * use this interface, and some of the events will be batched if your implementation is slow to
+ * handle the incoming input. The events received by these callbacks are never null.
+ */
+class InputConsumerCallbacks {
+public:
+    virtual ~InputConsumerCallbacks(){};
+    virtual void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) = 0;
+    virtual void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) = 0;
+    /**
+     * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
+     * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
+     * std::nullopt requestedFrameTime to receive the pending motion event(s).
+     * @param pendingBatchSource the source of the pending batch.
+     */
+    virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
+    virtual void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) = 0;
+    virtual void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) = 0;
+    virtual void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) = 0;
+    virtual void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) = 0;
+};
+
+/**
+ * Consumes input events from an input channel.
+ *
+ * This is a re-implementation of InputConsumer. At the moment it only supports resampling for
+ * single pointer events. A lot of the higher-level logic has been folded into this class, to make
+ * it easier to use. In the legacy class, InputConsumer, the consumption logic was partially handled
+ * in the jni layer, as well as various actions like adding the fd to the Choreographer.
+ *
+ * TODO(b/297226446): use this instead of "InputConsumer":
+ * - Add resampling for multiple pointer events.
+ * - Allow various resampling strategies to be specified
+ * - Delete the old "InputConsumer" and use this class instead, renaming it to "InputConsumer".
+ * - Add tracing
+ * - Update all tests to use the new InputConsumer
+ *
+ * This class is not thread-safe. We are currently allowing the constructor to run on any thread,
+ * but all of the remaining APIs should be invoked on the looper thread only.
+ */
+class InputConsumerNoResampling final {
+public:
+    /**
+     * @param callbacks are used to interact with InputConsumerNoResampling. They're called whenever
+     * the event is ready to consume.
+     * @param looper needs to be sp and not shared_ptr because it inherits from
+     * RefBase
+     * @param resampler the resampling strategy to use. If null, no resampling will be
+     * performed.
+     */
+    explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                       sp<Looper> looper, InputConsumerCallbacks& callbacks,
+                                       std::unique_ptr<Resampler> resampler);
+
+    ~InputConsumerNoResampling();
+
+    /**
+     * Must be called exactly once for each event received through the callbacks.
+     */
+    void finishInputEvent(uint32_t seq, bool handled);
+    void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
+    /**
+     * If you want to consume all events immediately (disable batching), then you still must call
+     * this. For requestedFrameTime, use a std::nullopt. It is not guaranteed that the consumption
+     * will occur at requestedFrameTime. The resampling strategy may modify it.
+     * @param requestedFrameTime the time up to which consume the events. When there's double (or
+     * triple) buffering, you may want to not consume all events currently available, because you
+     * could be still working on an older frame, but there could already have been events that
+     * arrived that are more recent.
+     * @return whether any events were actually consumed
+     */
+    bool consumeBatchedInputEvents(std::optional<nsecs_t> requestedFrameTime);
+
+    /**
+     * Returns true when there is *likely* a pending batch or a pending event in the channel.
+     *
+     * This is only a performance hint and may return false negative results. Clients should not
+     * rely on availability of the message based on the return value.
+     */
+    bool probablyHasInput() const;
+
+    std::string getName() { return mChannel->getName(); }
+
+    std::string dump() const;
+
+private:
+    std::shared_ptr<InputChannel> mChannel;
+    sp<Looper> mLooper;
+    InputConsumerCallbacks& mCallbacks;
+    std::unique_ptr<Resampler> mResampler;
+
+    // Looper-related infrastructure
+    /**
+     * This class is needed to associate the function "handleReceiveCallback" with the provided
+     * looper. The callback sent to the looper is RefBase - based, so we can't just send a reference
+     * of this class directly to the looper.
+     */
+    class LooperEventCallback : public LooperCallback {
+    public:
+        LooperEventCallback(std::function<int(int events)> callback) : mCallback(callback) {}
+        int handleEvent(int /*fd*/, int events, void* /*data*/) override {
+            return mCallback(events);
+        }
+
+    private:
+        std::function<int(int events)> mCallback;
+    };
+    sp<LooperEventCallback> mCallback;
+    /**
+     * The actual code that executes when the looper encounters available data on the InputChannel.
+     */
+    int handleReceiveCallback(int events);
+    int mFdEvents;
+    void setFdEvents(int events);
+
+    void ensureCalledOnLooperThread(const char* func) const;
+
+    // Event-reading infrastructure
+    /**
+     * A fifo queue of events to be sent to the InputChannel. We can't send all InputMessages to
+     * the channel immediately when they are produced, because it's possible that the InputChannel
+     * is blocked (if the channel buffer is full). When that happens, we don't want to drop the
+     * events. Therefore, events should only be erased from the queue after they've been
+     * successfully written to the InputChannel.
+     */
+    std::queue<InputMessage> mOutboundQueue;
+    /**
+     * Try to send all of the events in mOutboundQueue over the InputChannel. Not all events might
+     * actually get sent, because it's possible that the channel is blocked.
+     */
+    void processOutboundEvents();
+
+    /**
+     * The time at which each event with the sequence number 'seq' was consumed.
+     * This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
+     * This collection is populated when the event is received, and the entries are erased when the
+     * events are finished. It should not grow infinitely because if an event is not ack'd, ANR
+     * will be raised for that connection, and no further events will be posted to that channel.
+     */
+    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
+    /**
+     * Find and return the consumeTime associated with the provided sequence number. Crashes if
+     * the provided seq number is not found.
+     */
+    nsecs_t popConsumeTime(uint32_t seq);
+
+    // Event reading and processing
+    /**
+     * Read all of the available events from the InputChannel
+     */
+    std::vector<InputMessage> readAllMessages();
+
+    /**
+     * Send InputMessage to the corresponding InputConsumerCallbacks function.
+     * @param msg
+     */
+    void handleMessage(const InputMessage& msg) const;
+
+    // Batching
+    /**
+     * Batch messages that can be batched. When an unbatchable message is encountered, send it
+     * to the InputConsumerCallbacks immediately. If there are batches remaining,
+     * notify InputConsumerCallbacks.
+     */
+    void handleMessages(std::vector<InputMessage>&& messages);
+    /**
+     * Batched InputMessages, per deviceId.
+     * For each device, we are storing a queue of batched messages. These will all be collapsed into
+     * a single MotionEvent (up to a specific requestedFrameTime) when the consumer calls
+     * `consumeBatchedInputEvents`.
+     */
+    std::map<DeviceId, std::queue<InputMessage>> mBatches;
+    /**
+     * Creates a MotionEvent by consuming samples from the provided queue. If one message has
+     * eventTime > adjustedFrameTime, all subsequent messages in the queue will be skipped. It is
+     * assumed that messages are queued in chronological order. In other words, only events that
+     * occurred prior to the adjustedFrameTime will be consumed.
+     * @param requestedFrameTime the time up to which to consume events.
+     * @param messages the queue of messages to consume from
+     */
+    std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>> createBatchedMotionEvent(
+            const nsecs_t requestedFrameTime, std::queue<InputMessage>& messages);
+
+    /**
+     * Consumes the batched input events, optionally allowing the caller to specify a device id
+     * and/or requestedFrameTime threshold. It is not guaranteed that consumption will occur at
+     * requestedFrameTime.
+     * @param deviceId The device id from which to consume events. If std::nullopt, consumes events
+     * from any device id.
+     * @param requestedFrameTime The time up to which consume the events. If std::nullopt, consumes
+     * input events with any timestamp.
+     * @return Whether or not any events were consumed.
+     */
+    bool consumeBatchedInputEvents(std::optional<DeviceId> deviceId,
+                                   std::optional<nsecs_t> requestedFrameTime);
+    /**
+     * A map from a single sequence number to several sequence numbers. This is needed because of
+     * batching. When batching is enabled, a single MotionEvent will contain several samples. Each
+     * sample came from an individual InputMessage of Type::Motion, and therefore will have to be
+     * finished individually. Therefore, when the app calls "finish" on a (possibly batched)
+     * MotionEvent, we will need to check this map in case there are multiple sequence numbers
+     * associated with a single number that the app provided.
+     *
+     * For example:
+     * Suppose we received 4 InputMessage's of type Motion, with action MOVE:
+     * InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)   InputMessage(MOVE)
+     *    seq=10               seq=11               seq=12               seq=13
+     * The app consumed them all as a batch, which means that the app received a single MotionEvent
+     * with historySize=3 and seq = 10.
+     *
+     * This map will look like:
+     * {
+     *   10: [11, 12, 13],
+     * }
+     * So the sequence number 10 will have 3 other sequence numbers associated with it.
+     * When the app calls 'finish' for seq=10, we need to call 'finish' 4 times total, for sequence
+     * numbers 10, 11, 12, 13. The app is not aware of the sequence numbers of each sample inside
+     * the batched MotionEvent that it received.
+     */
+    std::map<uint32_t, std::vector<uint32_t>> mBatchedSequenceNumbers;
+};
+
+} // namespace android
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 57b659d..1a48239 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -115,6 +115,8 @@
     ACCURACY_LOW = 1,
     ACCURACY_MEDIUM = 2,
     ACCURACY_HIGH = 3,
+
+    ftl_last = ACCURACY_HIGH,
 };
 
 enum class InputDeviceSensorReportingMode : int32_t {
@@ -128,8 +130,9 @@
     INPUT = 0,
     PLAYER_ID = 1,
     KEYBOARD_BACKLIGHT = 2,
+    KEYBOARD_MIC_MUTE = 3,
 
-    ftl_last = KEYBOARD_BACKLIGHT
+    ftl_last = KEYBOARD_MIC_MUTE
 };
 
 enum class InputDeviceLightCapability : uint32_t {
@@ -277,8 +280,8 @@
 
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
-                    bool isExternal, bool hasMic, int32_t associatedDisplayId,
-                    InputDeviceViewBehavior viewBehavior = {{}});
+                    bool isExternal, bool hasMic, ui::LogicalDisplayId associatedDisplayId,
+                    InputDeviceViewBehavior viewBehavior = {{}}, bool enabled = true);
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -345,7 +348,10 @@
     }
     inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
 
-    inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
+    inline ui::LogicalDisplayId getAssociatedDisplayId() const { return mAssociatedDisplayId; }
+
+    inline void setEnabled(bool enabled) { mEnabled = enabled; }
+    inline bool isEnabled() const { return mEnabled; }
 
 private:
     int32_t mId;
@@ -360,7 +366,8 @@
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
     std::optional<InputDeviceUsiVersion> mUsiVersion;
-    int32_t mAssociatedDisplayId;
+    ui::LogicalDisplayId mAssociatedDisplayId{ui::LogicalDisplayId::INVALID};
+    bool mEnabled;
 
     bool mHasVibrator;
     bool mHasBattery;
@@ -382,6 +389,7 @@
     CONFIGURATION = 0,     /* .idc file */
     KEY_LAYOUT = 1,        /* .kl file */
     KEY_CHARACTER_MAP = 2, /* .kcm file */
+    ftl_last = KEY_CHARACTER_MAP,
 };
 
 /*
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 2d23b97..5bd5070 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -18,8 +18,9 @@
 
 #include <android/input.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
+#include <input/InputTransport.h>
+#include <ui/LogicalDisplayId.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -45,6 +46,11 @@
 
     PointerBuilder& y(float y) { return axis(AMOTION_EVENT_AXIS_Y, y); }
 
+    PointerBuilder& isResampled(bool isResampled) {
+        mCoords.isResampled = isResampled;
+        return *this;
+    }
+
     PointerBuilder& axis(int32_t axis, float value) {
         mCoords.setAxisValue(axis, value);
         return *this;
@@ -59,6 +65,87 @@
     PointerCoords mCoords;
 };
 
+class InputMessageBuilder {
+public:
+    InputMessageBuilder(InputMessage::Type type, uint32_t seq) : mType{type}, mSeq{seq} {}
+
+    InputMessageBuilder& eventId(int32_t eventId) {
+        mEventId = eventId;
+        return *this;
+    }
+
+    InputMessageBuilder& eventTime(nsecs_t eventTime) {
+        mEventTime = eventTime;
+        return *this;
+    }
+
+    InputMessageBuilder& deviceId(DeviceId deviceId) {
+        mDeviceId = deviceId;
+        return *this;
+    }
+
+    InputMessageBuilder& source(int32_t source) {
+        mSource = source;
+        return *this;
+    }
+
+    InputMessageBuilder& displayId(ui::LogicalDisplayId displayId) {
+        mDisplayId = displayId;
+        return *this;
+    }
+
+    InputMessageBuilder& action(int32_t action) {
+        mAction = action;
+        return *this;
+    }
+
+    InputMessageBuilder& downTime(nsecs_t downTime) {
+        mDownTime = downTime;
+        return *this;
+    }
+
+    InputMessageBuilder& pointer(PointerBuilder pointerBuilder) {
+        mPointers.push_back(pointerBuilder);
+        return *this;
+    }
+
+    InputMessage build() const {
+        InputMessage message{};
+        // Header
+        message.header.type = mType;
+        message.header.seq = mSeq;
+        // Body
+        message.body.motion.eventId = mEventId;
+        message.body.motion.pointerCount = mPointers.size();
+        message.body.motion.eventTime = mEventTime;
+        message.body.motion.deviceId = mDeviceId;
+        message.body.motion.source = mSource;
+        message.body.motion.displayId = mDisplayId.val();
+        message.body.motion.action = mAction;
+        message.body.motion.downTime = mDownTime;
+
+        for (size_t i = 0; i < mPointers.size(); ++i) {
+            message.body.motion.pointers[i].properties = mPointers[i].buildProperties();
+            message.body.motion.pointers[i].coords = mPointers[i].buildCoords();
+        }
+        return message;
+    }
+
+private:
+    const InputMessage::Type mType;
+    const uint32_t mSeq;
+
+    int32_t mEventId{InputEvent::nextId()};
+    nsecs_t mEventTime{systemTime(SYSTEM_TIME_MONOTONIC)};
+    DeviceId mDeviceId{DEFAULT_DEVICE_ID};
+    int32_t mSource{AINPUT_SOURCE_TOUCHSCREEN};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
+    int32_t mAction{AMOTION_EVENT_ACTION_MOVE};
+    nsecs_t mDownTime{mEventTime};
+
+    std::vector<PointerBuilder> mPointers;
+};
+
 class MotionEventBuilder {
 public:
     MotionEventBuilder(int32_t action, int32_t source) {
@@ -83,7 +170,7 @@
         return *this;
     }
 
-    MotionEventBuilder& displayId(int32_t displayId) {
+    MotionEventBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -118,7 +205,17 @@
         return *this;
     }
 
-    MotionEvent build() {
+    MotionEventBuilder& transform(ui::Transform t) {
+        mTransform = t;
+        return *this;
+    }
+
+    MotionEventBuilder& rawTransform(ui::Transform t) {
+        mRawTransform = t;
+        return *this;
+    }
+
+    MotionEvent build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -126,21 +223,22 @@
             pointerCoords.push_back(pointer.buildCoords());
         }
 
+        auto [xCursorPosition, yCursorPosition] =
+                std::make_pair(mRawXCursorPosition, mRawYCursorPosition);
         // Set mouse cursor position for the most common cases to avoid boilerplate.
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
-            mRawXCursorPosition = pointerCoords[0].getX();
-            mRawYCursorPosition = pointerCoords[0].getY();
+            !MotionEvent::isValidCursorPosition(xCursorPosition, yCursorPosition)) {
+            xCursorPosition = pointerCoords[0].getX();
+            yCursorPosition = pointerCoords[0].getY();
         }
 
         MotionEvent event;
-        static const ui::Transform kIdentityTransform;
         event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
                          mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState,
-                         MotionClassification::NONE, kIdentityTransform,
-                         /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
-                         mRawYCursorPosition, kIdentityTransform, mDownTime, mEventTime,
-                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
+                         MotionClassification::NONE, mTransform,
+                         /*xPrecision=*/0, /*yPrecision=*/0, xCursorPosition, yCursorPosition,
+                         mRawTransform, mDownTime, mEventTime, mPointers.size(),
+                         pointerProperties.data(), pointerCoords.data());
         return event;
     }
 
@@ -150,12 +248,14 @@
     int32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     int32_t mActionButton{0};
     int32_t mButtonState{0};
     int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    ui::Transform mTransform;
+    ui::Transform mRawTransform;
 
     std::vector<PointerBuilder> mPointers;
 };
@@ -198,7 +298,7 @@
         return *this;
     }
 
-    KeyEventBuilder& displayId(int32_t displayId) {
+    KeyEventBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -237,7 +337,7 @@
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mFlags{0};
     int32_t mKeyCode{AKEYCODE_UNKNOWN};
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 42dcd3c..0cd8720 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -263,7 +263,7 @@
      * Return DEAD_OBJECT if the channel's peer has been closed.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t sendMessage(const InputMessage* msg);
+    virtual status_t sendMessage(const InputMessage* msg);
 
     /* Receive a message sent by the other endpoint.
      *
@@ -275,14 +275,14 @@
      * Return DEAD_OBJECT if the channel's peer has been closed.
      * Other errors probably indicate that the channel is broken.
      */
-    status_t receiveMessage(InputMessage* msg);
+    virtual android::base::Result<InputMessage> receiveMessage();
 
     /* Tells whether there is a message in the channel available to be received.
      *
      * This is only a performance hint and may return false negative results. Clients should not
      * rely on availability of the message based on the return value.
      */
-    bool probablyHasInput() const;
+    virtual bool probablyHasInput() const;
 
     /* Wait until there is a message in the channel.
      *
@@ -323,11 +323,12 @@
      */
     sp<IBinder> getConnectionToken() const;
 
+protected:
+    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
+
 private:
     static std::unique_ptr<InputChannel> create(const std::string& name,
                                                 android::base::unique_fd fd, sp<IBinder> token);
-
-    InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
 };
 
 /*
@@ -353,22 +354,24 @@
      * Other errors probably indicate that the channel is broken.
      */
     status_t publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
-                             int32_t displayId, std::array<uint8_t, 32> hmac, int32_t action,
-                             int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-                             int32_t repeatCount, nsecs_t downTime, nsecs_t eventTime);
+                             ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                             int32_t metaState, int32_t repeatCount, nsecs_t downTime,
+                             nsecs_t eventTime);
 
     /* Publishes a motion event to the input channel.
      *
      * Returns OK on success.
      * Returns WOULD_BLOCK if the channel is full.
      * Returns DEAD_OBJECT if the channel's peer has been closed.
-     * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS.
+     * Returns BAD_VALUE if seq is 0 or if pointerCount is less than 1 or greater than MAX_POINTERS,
+     * or if the verifier is enabled and the event failed verification upon publishing.
      * Other errors probably indicate that the channel is broken.
      */
     status_t publishMotionEvent(uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
-                                int32_t displayId, std::array<uint8_t, 32> hmac, int32_t action,
-                                int32_t actionButton, int32_t flags, int32_t edgeFlags,
-                                int32_t metaState, int32_t buttonState,
+                                ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                                int32_t action, int32_t actionButton, int32_t flags,
+                                int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
                                 float yCursorPosition, const ui::Transform& rawTransform,
@@ -451,236 +454,4 @@
     InputVerifier mInputVerifier;
 };
 
-/*
- * Consumes input events from an input channel.
- */
-class InputConsumer {
-public:
-    /* Create a consumer associated with an input channel. */
-    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel);
-    /* Create a consumer associated with an input channel, override resampling system property */
-    explicit InputConsumer(const std::shared_ptr<InputChannel>& channel,
-                           bool enableTouchResampling);
-
-    /* Destroys the consumer and releases its input channel. */
-    ~InputConsumer();
-
-    /* Gets the underlying input channel. */
-    inline std::shared_ptr<InputChannel> getChannel() { return mChannel; }
-
-    /* Consumes an input event from the input channel and copies its contents into
-     * an InputEvent object created using the specified factory.
-     *
-     * Tries to combine a series of move events into larger batches whenever possible.
-     *
-     * If consumeBatches is false, then defers consuming pending batched events if it
-     * is possible for additional samples to be added to them later.  Call hasPendingBatch()
-     * to determine whether a pending batch is available to be consumed.
-     *
-     * If consumeBatches is true, then events are still batched but they are consumed
-     * immediately as soon as the input channel is exhausted.
-     *
-     * The frameTime parameter specifies the time when the current display frame started
-     * rendering in the CLOCK_MONOTONIC time base, or -1 if unknown.
-     *
-     * The returned sequence number is never 0 unless the operation failed.
-     *
-     * Returns OK on success.
-     * Returns WOULD_BLOCK if there is no event present.
-     * Returns DEAD_OBJECT if the channel's peer has been closed.
-     * Returns NO_MEMORY if the event could not be created.
-     * Other errors probably indicate that the channel is broken.
-     */
-    status_t consume(InputEventFactoryInterface* factory, bool consumeBatches, nsecs_t frameTime,
-                     uint32_t* outSeq, InputEvent** outEvent);
-
-    /* Sends a finished signal to the publisher to inform it that the message
-     * with the specified sequence number has finished being process and whether
-     * the message was handled by the consumer.
-     *
-     * Returns OK on success.
-     * Returns BAD_VALUE if seq is 0.
-     * Other errors probably indicate that the channel is broken.
-     */
-    status_t sendFinishedSignal(uint32_t seq, bool handled);
-
-    status_t sendTimeline(int32_t inputEventId,
-                          std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
-
-    /* Returns true if there is a pending batch.
-     *
-     * Should be called after calling consume() with consumeBatches == false to determine
-     * whether consume() should be called again later on with consumeBatches == true.
-     */
-    bool hasPendingBatch() const;
-
-    /* Returns the source of first pending batch if exist.
-     *
-     * Should be called after calling consume() with consumeBatches == false to determine
-     * whether consume() should be called again later on with consumeBatches == true.
-     */
-    int32_t getPendingBatchSource() const;
-
-    /* Returns true when there is *likely* a pending batch or a pending event in the channel.
-     *
-     * This is only a performance hint and may return false negative results. Clients should not
-     * rely on availability of the message based on the return value.
-     */
-    bool probablyHasInput() const;
-
-    std::string dump() const;
-
-private:
-    // True if touch resampling is enabled.
-    const bool mResampleTouch;
-
-    std::shared_ptr<InputChannel> mChannel;
-
-    // The current input message.
-    InputMessage mMsg;
-
-    // True if mMsg contains a valid input message that was deferred from the previous
-    // call to consume and that still needs to be handled.
-    bool mMsgDeferred;
-
-    // Batched motion events per device and source.
-    struct Batch {
-        std::vector<InputMessage> samples;
-    };
-    std::vector<Batch> mBatches;
-
-    // Touch state per device and source, only for sources of class pointer.
-    struct History {
-        nsecs_t eventTime;
-        BitSet32 idBits;
-        int32_t idToIndex[MAX_POINTER_ID + 1];
-        PointerCoords pointers[MAX_POINTERS];
-
-        void initializeFrom(const InputMessage& msg) {
-            eventTime = msg.body.motion.eventTime;
-            idBits.clear();
-            for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-                uint32_t id = msg.body.motion.pointers[i].properties.id;
-                idBits.markBit(id);
-                idToIndex[id] = i;
-                pointers[i].copyFrom(msg.body.motion.pointers[i].coords);
-            }
-        }
-
-        void initializeFrom(const History& other) {
-            eventTime = other.eventTime;
-            idBits = other.idBits; // temporary copy
-            for (size_t i = 0; i < other.idBits.count(); i++) {
-                uint32_t id = idBits.clearFirstMarkedBit();
-                int32_t index = other.idToIndex[id];
-                idToIndex[id] = index;
-                pointers[index].copyFrom(other.pointers[index]);
-            }
-            idBits = other.idBits; // final copy
-        }
-
-        const PointerCoords& getPointerById(uint32_t id) const {
-            return pointers[idToIndex[id]];
-        }
-
-        bool hasPointerId(uint32_t id) const {
-            return idBits.hasBit(id);
-        }
-    };
-    struct TouchState {
-        int32_t deviceId;
-        int32_t source;
-        size_t historyCurrent;
-        size_t historySize;
-        History history[2];
-        History lastResample;
-
-        void initialize(int32_t deviceId, int32_t source) {
-            this->deviceId = deviceId;
-            this->source = source;
-            historyCurrent = 0;
-            historySize = 0;
-            lastResample.eventTime = 0;
-            lastResample.idBits.clear();
-        }
-
-        void addHistory(const InputMessage& msg) {
-            historyCurrent ^= 1;
-            if (historySize < 2) {
-                historySize += 1;
-            }
-            history[historyCurrent].initializeFrom(msg);
-        }
-
-        const History* getHistory(size_t index) const {
-            return &history[(historyCurrent + index) & 1];
-        }
-
-        bool recentCoordinatesAreIdentical(uint32_t id) const {
-            // Return true if the two most recently received "raw" coordinates are identical
-            if (historySize < 2) {
-                return false;
-            }
-            if (!getHistory(0)->hasPointerId(id) || !getHistory(1)->hasPointerId(id)) {
-                return false;
-            }
-            float currentX = getHistory(0)->getPointerById(id).getX();
-            float currentY = getHistory(0)->getPointerById(id).getY();
-            float previousX = getHistory(1)->getPointerById(id).getX();
-            float previousY = getHistory(1)->getPointerById(id).getY();
-            if (currentX == previousX && currentY == previousY) {
-                return true;
-            }
-            return false;
-        }
-    };
-    std::vector<TouchState> mTouchStates;
-
-    // Chain of batched sequence numbers.  When multiple input messages are combined into
-    // a batch, we append a record here that associates the last sequence number in the
-    // batch with the previous one.  When the finished signal is sent, we traverse the
-    // chain to individually finish all input messages that were part of the batch.
-    struct SeqChain {
-        uint32_t seq;   // sequence number of batched input message
-        uint32_t chain; // sequence number of previous batched input message
-    };
-    std::vector<SeqChain> mSeqChains;
-
-    // The time at which each event with the sequence number 'seq' was consumed.
-    // This data is provided in 'finishInputEvent' so that the receiving end can measure the latency
-    // This collection is populated when the event is received, and the entries are erased when the
-    // events are finished. It should not grow infinitely because if an event is not ack'd, ANR
-    // will be raised for that connection, and no further events will be posted to that channel.
-    std::unordered_map<uint32_t /*seq*/, nsecs_t /*consumeTime*/> mConsumeTimes;
-
-    status_t consumeBatch(InputEventFactoryInterface* factory,
-            nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent);
-    status_t consumeSamples(InputEventFactoryInterface* factory,
-            Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent);
-
-    void updateTouchState(InputMessage& msg);
-    void resampleTouchState(nsecs_t frameTime, MotionEvent* event,
-            const InputMessage *next);
-
-    ssize_t findBatch(int32_t deviceId, int32_t source) const;
-    ssize_t findTouchState(int32_t deviceId, int32_t source) const;
-
-    nsecs_t getConsumeTime(uint32_t seq) const;
-    void popConsumeTime(uint32_t seq);
-    status_t sendUnchainedFinishedSignal(uint32_t seq, bool handled);
-
-    static void rewriteMessage(TouchState& state, InputMessage& msg);
-    static void initializeKeyEvent(KeyEvent* event, const InputMessage* msg);
-    static void initializeMotionEvent(MotionEvent* event, const InputMessage* msg);
-    static void initializeFocusEvent(FocusEvent* event, const InputMessage* msg);
-    static void initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg);
-    static void initializeDragEvent(DragEvent* event, const InputMessage* msg);
-    static void initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg);
-    static void addSample(MotionEvent* event, const InputMessage* msg);
-    static bool canAddSample(const Batch& batch, const InputMessage* msg);
-    static ssize_t findSampleNoLaterThan(const Batch& batch, nsecs_t time);
-
-    static bool isTouchResamplingEnabled();
-};
-
 } // namespace android
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index dfcf766..67b37b1 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -19,9 +19,7 @@
 #include <stdint.h>
 #include <list>
 
-#ifdef __linux__
 #include <binder/IBinder.h>
-#endif
 
 #include <android-base/result.h>
 #include <input/Input.h>
@@ -128,9 +126,9 @@
     bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
             Vector<KeyEvent>& outEvents) const;
 
-    /* Maps an Android key code to another Android key code. This mapping is applied after scanCode
-     * and usageCodes are mapped to corresponding Android Keycode */
-    void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
+    /* Maps some Android key code to another Android key code. This mapping is applied after
+     * scanCode and usageCodes are mapped to corresponding Android Keycode */
+    void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
 
     /* Maps a scan code and usage code to a key code, in case this key map overrides
      * the mapping in some way. */
@@ -144,13 +142,11 @@
     std::pair<int32_t /*keyCode*/, int32_t /*metaState*/> applyKeyBehavior(int32_t keyCode,
                                                                            int32_t metaState) const;
 
-#ifdef __linux__
     /* Reads a key map from a parcel. */
     static std::unique_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
 
     /* Writes a key map to a parcel. */
     void writeToParcel(Parcel* parcel) const;
-#endif
 
     bool operator==(const KeyCharacterMap& other) const = default;
 
diff --git a/include/input/KeyboardClassifier.h b/include/input/KeyboardClassifier.h
new file mode 100644
index 0000000..457d474
--- /dev/null
+++ b/include/input/KeyboardClassifier.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/result.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+
+#include "rust/cxx.h"
+
+namespace android {
+
+namespace input {
+namespace keyboardClassifier {
+struct KeyboardClassifier;
+}
+} // namespace input
+
+/*
+ * Keyboard classifier to classify keyboard into alphabetic and non-alphabetic keyboards
+ */
+class KeyboardClassifier {
+public:
+    KeyboardClassifier();
+    /**
+     * Get the type of keyboard that the classifier currently believes the device to be.
+     */
+    KeyboardType getKeyboardType(DeviceId deviceId);
+    void notifyKeyboardChanged(DeviceId deviceId, const InputDeviceIdentifier& identifier,
+                               uint32_t deviceClasses);
+    void processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState);
+
+private:
+    std::optional<rust::Box<android::input::keyboardClassifier::KeyboardClassifier>>
+            mRustClassifier;
+    std::unordered_map<DeviceId, KeyboardType> mKeyboardTypeMap;
+};
+
+} // namespace android
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 3b6e401..200c301 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <array>
 #include <cstdint>
 #include <memory>
 #include <mutex>
@@ -28,6 +29,7 @@
 #include <android/sysprop/InputProperties.sysprop.h>
 #include <input/Input.h>
 #include <input/MotionPredictorMetricsManager.h>
+#include <input/RingBuffer.h>
 #include <input/TfLiteMotionPredictor.h>
 #include <utils/Timers.h> // for nsecs_t
 
@@ -37,6 +39,36 @@
     return sysprop::InputProperties::enable_motion_prediction().value_or(true);
 }
 
+// Tracker to calculate jerk from motion position samples.
+class JerkTracker {
+public:
+    // Initialize the tracker. If normalizedDt is true, assume that each sample pushed has dt=1.
+    // alpha is the coefficient of the first-order IIR filter for jerk. A factor of 1 results
+    // in no smoothing.
+    JerkTracker(bool normalizedDt, float alpha);
+
+    // Add a position to the tracker and update derivative estimates.
+    void pushSample(int64_t timestamp, float xPos, float yPos);
+
+    // Reset JerkTracker for a new motion input.
+    void reset();
+
+    // Return last jerk calculation, if enough samples have been collected.
+    // Jerk is defined as the 3rd derivative of position (change in
+    // acceleration) and has the units of d^3p/dt^3.
+    std::optional<float> jerkMagnitude() const;
+
+private:
+    const bool mNormalizedDt;
+    // Coefficient of first-order IIR filter to smooth jerk calculation.
+    const float mAlpha;
+
+    RingBuffer<int64_t> mTimestamps{4};
+    std::array<float, 4> mXDerivatives{}; // [x, x', x'', x''']
+    std::array<float, 4> mYDerivatives{}; // [y, y', y'', y''']
+    float mJerkMagnitude;
+};
+
 /**
  * Given a set of MotionEvents for the current gesture, predict the motion. The returned MotionEvent
  * contains a set of samples in the future.
@@ -98,9 +130,16 @@
     std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers;
     std::optional<MotionEvent> mLastEvent;
 
-    std::optional<MotionPredictorMetricsManager> mMetricsManager;
+    std::unique_ptr<JerkTracker> mJerkTracker;
+
+    std::unique_ptr<MotionPredictorMetricsManager> mMetricsManager;
 
     const ReportAtomFunction mReportAtomFunction;
+
+    // Initialize prediction model and associated objects.
+    // Called during lazy initialization.
+    // TODO: b/210158587 Consider removing lazy initialization.
+    void initializeObjects();
 };
 
 } // namespace android
diff --git a/include/input/OWNERS b/include/input/OWNERS
index c88bfe9..21d208f 100644
--- a/include/input/OWNERS
+++ b/include/input/OWNERS
@@ -1 +1,2 @@
+# Bug component: 136048
 include platform/frameworks/base:/INPUT_OWNERS
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
new file mode 100644
index 0000000..dcb25b7
--- /dev/null
+++ b/include/input/Resampler.h
@@ -0,0 +1,154 @@
+/**
+ * Copyright 2024 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 <chrono>
+#include <optional>
+#include <vector>
+
+#include <input/Input.h>
+#include <input/InputTransport.h>
+#include <input/RingBuffer.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+/**
+ * Resampler is an interface for resampling MotionEvents. Every resampling implementation
+ * must use this interface to enable resampling inside InputConsumer's logic.
+ */
+struct Resampler {
+    virtual ~Resampler() = default;
+
+    /**
+     * Tries to resample motionEvent at frameTime. The provided frameTime must be greater than
+     * the latest sample time of motionEvent. It is not guaranteed that resampling occurs at
+     * frameTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
+     * may be resampled by another method, or not resampled at all. Furthermore, it is the
+     * implementer's responsibility to guarantee the following:
+     * - If resampling occurs, a single additional sample should be added to motionEvent. That is,
+     * if motionEvent had N samples before being passed to Resampler, then it will have N + 1
+     * samples by the end of the resampling. No other field of motionEvent should be modified.
+     * - If resampling does not occur, then motionEvent must not be modified in any way.
+     */
+    virtual void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
+                                     const InputMessage* futureSample) = 0;
+
+    /**
+     * Returns resample latency. Resample latency is the time difference between frame time and
+     * resample time. More precisely, let frameTime and resampleTime be two timestamps, and
+     * frameTime > resampleTime. Resample latency is defined as frameTime - resampleTime.
+     */
+    virtual std::chrono::nanoseconds getResampleLatency() const = 0;
+};
+
+class LegacyResampler final : public Resampler {
+public:
+    /**
+     * Tries to resample `motionEvent` at `frameTime` by adding a resampled sample at the end of
+     * `motionEvent` with eventTime equal to `resampleTime` and pointer coordinates determined by
+     * linear interpolation or linear extrapolation. An earlier `resampleTime` will be used if
+     * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is
+     * not null, interpolation will occur. If `futureSample` is null and there is enough historical
+     * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
+     * `motionEvent` is unmodified.
+     */
+    void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
+                             const InputMessage* futureSample) override;
+
+    std::chrono::nanoseconds getResampleLatency() const override;
+
+private:
+    struct Pointer {
+        PointerProperties properties;
+        PointerCoords coords;
+    };
+
+    struct Sample {
+        std::chrono::nanoseconds eventTime;
+        std::vector<Pointer> pointers;
+
+        std::vector<PointerCoords> asPointerCoords() const {
+            std::vector<PointerCoords> pointersCoords;
+            for (const Pointer& pointer : pointers) {
+                pointersCoords.push_back(pointer.coords);
+            }
+            return pointersCoords;
+        }
+    };
+
+    /**
+     * Keeps track of the previous MotionEvent deviceId to enable comparison between the previous
+     * and the current deviceId.
+     */
+    std::optional<DeviceId> mPreviousDeviceId;
+
+    /**
+     * Up to two latest samples from MotionEvent. Updated every time resampleMotionEvent is called.
+     * Note: We store up to two samples in order to simplify the implementation. Although,
+     * calculations are possible with only one previous sample.
+     */
+    RingBuffer<Sample> mLatestSamples{/*capacity=*/2};
+
+    /**
+     * Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. If
+     * motionEvent has fewer samples than mLatestSamples.capacity(), then the available samples are
+     * added to mLatestSamples.
+     */
+    void updateLatestSamples(const MotionEvent& motionEvent);
+
+    static Sample messageToSample(const InputMessage& message);
+
+    /**
+     * Checks if auxiliary sample has the same pointer properties of target sample. That is,
+     * auxiliary pointer IDs must appear in the same order as target pointer IDs, their toolType
+     * must match and be resampleable.
+     */
+    static bool pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary);
+
+    /**
+     * Checks if there are necessary conditions to interpolate. For example, interpolation cannot
+     * take place if samples are too far apart in time. mLatestSamples must have at least one sample
+     * when canInterpolate is invoked.
+     */
+    bool canInterpolate(const InputMessage& futureSample) const;
+
+    /**
+     * Returns a sample interpolated between the latest sample of mLatestSamples and futureSample,
+     * if the conditions from canInterpolate are satisfied. Otherwise, returns nullopt.
+     * mLatestSamples must have at least one sample when attemptInterpolation is called.
+     */
+    std::optional<Sample> attemptInterpolation(std::chrono::nanoseconds resampleTime,
+                                               const InputMessage& futureSample) const;
+
+    /**
+     * Checks if there are necessary conditions to extrapolate. That is, there are at least two
+     * samples in mLatestSamples, and delta is bounded within a time interval.
+     */
+    bool canExtrapolate() const;
+
+    /**
+     * Returns a sample extrapolated from the two samples of mLatestSamples, if the conditions from
+     * canExtrapolate are satisfied. The returned sample either has eventTime equal to resampleTime,
+     * or an earlier time if resampleTime is too far in the future. If canExtrapolate returns false,
+     * this function returns nullopt.
+     */
+    std::optional<Sample> attemptExtrapolation(std::chrono::nanoseconds resampleTime) const;
+
+    inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent);
+};
+} // namespace android
diff --git a/include/input/TfLiteMotionPredictor.h b/include/input/TfLiteMotionPredictor.h
index 2edc138..49e909e 100644
--- a/include/input/TfLiteMotionPredictor.h
+++ b/include/input/TfLiteMotionPredictor.h
@@ -105,6 +105,14 @@
         // The noise floor for predictions.
         // Distances (r) less than this should be discarded as noise.
         float distanceNoiseFloor = 0;
+
+        // Low and high jerk thresholds (with normalized dt = 1) for predictions.
+        // High jerk means more predictions will be pruned, vice versa for low.
+        float lowJerk = 0;
+        float highJerk = 0;
+
+        // Coefficient for the first-order IIR filter for jerk calculation.
+        float jerkAlpha = 1;
     };
 
     // Creates a model from an encoded Flatbuffer model.
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
index 222dac8..b6c6305 100644
--- a/include/input/VirtualInputDevice.h
+++ b/include/input/VirtualInputDevice.h
@@ -17,14 +17,30 @@
 #pragma once
 
 #include <android-base/unique_fd.h>
+#include <input/Input.h>
+#include <map>
 
 namespace android {
 
+enum class DeviceType {
+    KEYBOARD,
+    MOUSE,
+    TOUCHSCREEN,
+    DPAD,
+    STYLUS,
+    ROTARY_ENCODER,
+};
+
+android::base::unique_fd openUinput(const char* readableName, int32_t vendorId, int32_t productId,
+                                    const char* phys, DeviceType deviceType, int32_t screenHeight,
+                                    int32_t screenWidth);
+
 enum class UinputAction {
     RELEASE = 0,
     PRESS = 1,
     MOVE = 2,
     CANCEL = 3,
+    ftl_last = CANCEL,
 };
 
 class VirtualInputDevice {
@@ -77,6 +93,8 @@
 
 private:
     static const std::map<int, int> BUTTON_CODE_MAPPING;
+    int32_t mAccumulatedHighResScrollX;
+    int32_t mAccumulatedHighResScrollY;
 };
 
 class VirtualTouchscreen : public VirtualInputDevice {
@@ -122,4 +140,14 @@
     bool handleStylusUp(uint16_t tool, std::chrono::nanoseconds eventTime);
 };
 
+class VirtualRotaryEncoder : public VirtualInputDevice {
+public:
+    VirtualRotaryEncoder(android::base::unique_fd fd);
+    virtual ~VirtualRotaryEncoder() override;
+    bool writeScrollEvent(float scrollAmount, std::chrono::nanoseconds eventTime);
+
+private:
+    int32_t mAccumulatedHighResScrollAmount;
+};
+
 } // namespace android
diff --git a/include/powermanager/HalResult.h b/include/powermanager/HalResult.h
new file mode 100644
index 0000000..7fe3822
--- /dev/null
+++ b/include/powermanager/HalResult.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *                        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/binder_auto_utils.h>
+#include <android/binder_status.h>
+#include <android/hardware/power/1.0/IPower.h>
+#include <binder/Status.h>
+#include <hidl/HidlSupport.h>
+#include <string>
+
+namespace android::power {
+
+static bool checkUnsupported(const ndk::ScopedAStatus& ndkStatus) {
+    return ndkStatus.getExceptionCode() == EX_UNSUPPORTED_OPERATION ||
+            ndkStatus.getStatus() == STATUS_UNKNOWN_TRANSACTION;
+}
+
+static bool checkUnsupported(const binder::Status& status) {
+    return status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
+            status.transactionError() == UNKNOWN_TRANSACTION;
+}
+
+// Result of a call to the Power HAL wrapper, holding data if successful.
+template <typename T>
+class HalResult {
+public:
+    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
+    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
+    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
+    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
+        if (checkUnsupported(status)) {
+            return HalResult<T>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
+        return HalResult<T>::fromStatus(status, T{data});
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T&& data) {
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<T>::unsupported();
+        }
+        if (ndkStatus.isOk()) {
+            return HalResult<T>::ok(std::forward<T>(data));
+        }
+        return HalResult<T>::failed(std::string(ndkStatus.getDescription()));
+    }
+
+    static HalResult<T> fromStatus(const ndk::ScopedAStatus& ndkStatus, T& data) {
+        return HalResult<T>::fromStatus(ndkStatus, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
+        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
+        return HalResult<T>::fromReturn(ret, T{data});
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T&& data) {
+        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
+                          : HalResult<T>::failed(ret.description());
+    }
+
+    template <typename R>
+    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
+                                   T& data) {
+        return HalResult<T>::fromReturn(ret, status, T{data});
+    }
+
+    // This will throw std::bad_optional_access if this result is not ok.
+    const T& value() const { return mValue.value(); }
+    bool isOk() const { return !mUnsupported && mValue.has_value(); }
+    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::optional<T> mValue;
+    std::string mErrorMessage;
+    bool mUnsupported;
+
+    explicit HalResult(T&& value)
+          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
+    explicit HalResult(std::string errorMessage, bool unsupported)
+          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
+};
+
+// Empty result
+template <>
+class HalResult<void> {
+public:
+    static HalResult<void> ok() { return HalResult(); }
+    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
+    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
+
+    static HalResult<void> fromStatus(const binder::Status& status) {
+        if (checkUnsupported(status)) {
+            return HalResult<void>::unsupported();
+        }
+        if (status.isOk()) {
+            return HalResult<void>::ok();
+        }
+        return HalResult<void>::failed(std::string(status.toString8().c_str()));
+    }
+
+    static HalResult<void> fromStatus(const ndk::ScopedAStatus& ndkStatus) {
+        if (ndkStatus.isOk()) {
+            return HalResult<void>::ok();
+        }
+        if (checkUnsupported(ndkStatus)) {
+            return HalResult<void>::unsupported();
+        }
+        return HalResult<void>::failed(ndkStatus.getDescription());
+    }
+
+    template <typename R>
+    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
+    }
+
+    bool isOk() const { return !mUnsupported && !mFailed; }
+    bool isFailed() const { return !mUnsupported && mFailed; }
+    bool isUnsupported() const { return mUnsupported; }
+    const char* errorMessage() const { return mErrorMessage.c_str(); }
+
+private:
+    std::string mErrorMessage;
+    bool mFailed;
+    bool mUnsupported;
+
+    explicit HalResult(bool unsupported = false)
+          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
+    explicit HalResult(std::string errorMessage)
+          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
+};
+} // namespace android::power
diff --git a/include/powermanager/OWNERS b/include/powermanager/OWNERS
new file mode 100644
index 0000000..9f40e27
--- /dev/null
+++ b/include/powermanager/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/ADPF_OWNERS
\ No newline at end of file
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index c50bc4a..7e0bd5b 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -23,6 +23,7 @@
 #include <aidl/android/hardware/power/Mode.h>
 #include <android-base/thread_annotations.h>
 #include <powermanager/PowerHalWrapper.h>
+#include <powermanager/PowerHintSessionWrapper.h>
 
 namespace android {
 
@@ -38,6 +39,7 @@
 
     virtual std::unique_ptr<HalWrapper> connect();
     virtual void reset();
+    virtual int32_t getAidlVersion();
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -59,14 +61,13 @@
                                      int32_t durationMs) override;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode,
                                     bool enabled) override;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) override;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) override;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     virtual HalResult<int64_t> getHintSessionPreferredRate() override;
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(
             int tgid, int uid) override;
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index cbbfa59..ab66336 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -36,6 +36,8 @@
     static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
     static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
     static sp<hardware::power::V1_3::IPower> loadHidlV1_3();
+    // Returns aidl interface version, or 0 if AIDL is not used
+    static int32_t getAidlVersion();
 
 private:
     static std::mutex gHalMutex;
@@ -48,6 +50,8 @@
     static sp<hardware::power::V1_0::IPower> loadHidlV1_0Locked()
             EXCLUSIVE_LOCKS_REQUIRED(gHalMutex);
 
+    static int32_t gAidlInterfaceVersion;
+
     PowerHalLoader() = delete;
     ~PowerHalLoader() = delete;
 };
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index e2da014..6e347a9 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -26,6 +26,9 @@
 #include <android/hardware/power/1.1/IPower.h>
 #include <android/hardware/power/1.2/IPower.h>
 #include <android/hardware/power/1.3/IPower.h>
+#include <powermanager/HalResult.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
 #include <binder/Status.h>
 
 #include <utility>
@@ -41,134 +44,6 @@
     OFF = 2,
 };
 
-// Result of a call to the Power HAL wrapper, holding data if successful.
-template <typename T>
-class HalResult {
-public:
-    static HalResult<T> ok(T&& value) { return HalResult(std::forward<T>(value)); }
-    static HalResult<T> ok(T& value) { return HalResult<T>::ok(T{value}); }
-    static HalResult<T> failed(std::string msg) { return HalResult(msg, /* unsupported= */ false); }
-    static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
-
-    static HalResult<T> fromStatus(const binder::Status& status, T&& data) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(std::forward<T>(data));
-        }
-        return HalResult<T>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<T> fromStatus(const binder::Status& status, T& data) {
-        return HalResult<T>::fromStatus(status, T{data});
-    }
-
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T&& data) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<T>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<T>::ok(std::forward<T>(data));
-        }
-        return HalResult<T>::failed(std::string(status.getDescription()));
-    }
-
-    static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T& data) {
-        return HalResult<T>::fromStatus(status, T{data});
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T&& data) {
-        return ret.isOk() ? HalResult<T>::ok(std::forward<T>(data))
-                          : HalResult<T>::failed(ret.description());
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T& data) {
-        return HalResult<T>::fromReturn(ret, T{data});
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T&& data) {
-        return ret.isOk() ? HalResult<T>::fromStatus(status, std::forward<T>(data))
-                          : HalResult<T>::failed(ret.description());
-    }
-
-    template <typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
-                                   T& data) {
-        return HalResult<T>::fromReturn(ret, status, T{data});
-    }
-
-    // This will throw std::bad_optional_access if this result is not ok.
-    const T& value() const { return mValue.value(); }
-    bool isOk() const { return !mUnsupported && mValue.has_value(); }
-    bool isFailed() const { return !mUnsupported && !mValue.has_value(); }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::optional<T> mValue;
-    std::string mErrorMessage;
-    bool mUnsupported;
-
-    explicit HalResult(T&& value)
-          : mValue{std::move(value)}, mErrorMessage(), mUnsupported(false) {}
-    explicit HalResult(std::string errorMessage, bool unsupported)
-          : mValue(), mErrorMessage(std::move(errorMessage)), mUnsupported(unsupported) {}
-};
-
-// Empty result of a call to the Power HAL wrapper.
-template <>
-class HalResult<void> {
-public:
-    static HalResult<void> ok() { return HalResult(); }
-    static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
-    static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
-
-    static HalResult<void> fromStatus(const binder::Status& status) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.toString8().c_str()));
-    }
-
-    static HalResult<void> fromStatus(const ndk::ScopedAStatus& status) {
-        if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
-            return HalResult<void>::unsupported();
-        }
-        if (status.isOk()) {
-            return HalResult<void>::ok();
-        }
-        return HalResult<void>::failed(std::string(status.getDescription()));
-    }
-
-    template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
-        return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-    }
-
-    bool isOk() const { return !mUnsupported && !mFailed; }
-    bool isFailed() const { return !mUnsupported && mFailed; }
-    bool isUnsupported() const { return mUnsupported; }
-    const char* errorMessage() const { return mErrorMessage.c_str(); }
-
-private:
-    std::string mErrorMessage;
-    bool mFailed;
-    bool mUnsupported;
-
-    explicit HalResult(bool unsupported = false)
-          : mErrorMessage(), mFailed(false), mUnsupported(unsupported) {}
-    explicit HalResult(std::string errorMessage)
-          : mErrorMessage(std::move(errorMessage)), mFailed(true), mUnsupported(false) {}
-};
-
 // Wrapper for Power HAL handlers.
 class HalWrapper {
 public:
@@ -177,14 +52,13 @@
     virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                                      int32_t durationMs) = 0;
     virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                      int64_t durationNanos) = 0;
-    virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+            int64_t durationNanos) = 0;
+    virtual HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) = 0;
     virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
     virtual HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                        int uid) = 0;
@@ -200,14 +74,13 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
     HalResult<int64_t> getHintSessionPreferredRate() override;
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
                                                                                int uid) override;
@@ -285,14 +158,13 @@
     HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
                              int32_t durationMs) override;
     HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSession(
             int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
             int64_t durationNanos) override;
-    HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-    createHintSessionWithConfig(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
-                                int64_t durationNanos,
-                                aidl::android::hardware::power::SessionTag tag,
-                                aidl::android::hardware::power::SessionConfig* config) override;
+    HalResult<std::shared_ptr<PowerHintSessionWrapper>> createHintSessionWithConfig(
+            int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
+            aidl::android::hardware::power::SessionTag tag,
+            aidl::android::hardware::power::SessionConfig* config) override;
 
     HalResult<int64_t> getHintSessionPreferredRate() override;
     HalResult<aidl::android::hardware::power::ChannelConfig> getSessionChannel(int tgid,
@@ -307,14 +179,12 @@
     std::mutex mBoostMutex;
     std::mutex mModeMutex;
     std::shared_ptr<aidl::android::hardware::power::IPower> mHandle;
-    // Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
-    // Need to increase the array size if more boost supported.
-    std::array<
-            std::atomic<HalSupport>,
-            static_cast<int32_t>(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) +
-                    1>
+    std::array<HalSupport,
+               static_cast<int32_t>(
+                       *(ndk::enum_range<aidl::android::hardware::power::Boost>().end() - 1)) +
+                       1>
             mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
-    std::array<std::atomic<HalSupport>,
+    std::array<HalSupport,
                static_cast<int32_t>(
                        *(ndk::enum_range<aidl::android::hardware::power::Mode>().end() - 1)) +
                        1>
diff --git a/include/powermanager/PowerHintSessionWrapper.h b/include/powermanager/PowerHintSessionWrapper.h
new file mode 100644
index 0000000..ba6fe77
--- /dev/null
+++ b/include/powermanager/PowerHintSessionWrapper.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/ChannelConfig.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/SessionConfig.h>
+#include <android-base/thread_annotations.h>
+#include "HalResult.h"
+
+namespace android::power {
+
+// Wrapper for power hint sessions, which allows for better mocking,
+// support checking, and failure handling than using hint sessions directly
+class PowerHintSessionWrapper {
+public:
+    virtual ~PowerHintSessionWrapper() = default;
+    PowerHintSessionWrapper(
+            std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>&& session);
+    virtual HalResult<void> updateTargetWorkDuration(int64_t in_targetDurationNanos);
+    virtual HalResult<void> reportActualWorkDuration(
+            const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations);
+    virtual HalResult<void> pause();
+    virtual HalResult<void> resume();
+    virtual HalResult<void> close();
+    virtual HalResult<void> sendHint(::aidl::android::hardware::power::SessionHint in_hint);
+    virtual HalResult<void> setThreads(const std::vector<int32_t>& in_threadIds);
+    virtual HalResult<void> setMode(::aidl::android::hardware::power::SessionMode in_type,
+                                    bool in_enabled);
+    virtual HalResult<aidl::android::hardware::power::SessionConfig> getSessionConfig();
+
+private:
+    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mSession;
+    int32_t mInterfaceVersion;
+};
+
+} // namespace android::power
\ No newline at end of file
diff --git a/include/private/OWNERS b/include/private/OWNERS
new file mode 100644
index 0000000..db3ae48
--- /dev/null
+++ b/include/private/OWNERS
@@ -0,0 +1,4 @@
+# ADPF
+per-file thermal_private.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file performance_hint_private.h = file:platform/frameworks/base:/ADPF_OWNERS
+per-file system_health_private.h = file:platform/frameworks/base:/ADPF_OWNERS
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index d8f9db4..e5eee34 100644
--- a/include/private/performance_hint_private.h
+++ b/include/private/performance_hint_private.h
@@ -18,6 +18,7 @@
 #define ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
 
 #include <stdint.h>
+#include <android/performance_hint.h>
 
 __BEGIN_DECLS
 
@@ -75,6 +76,15 @@
     GPU_LOAD_RESET = 7,
 };
 
+// Allows access to PowerHAL's SessionTags without needing to import its AIDL
+enum class SessionTag : int32_t {
+  OTHER = 0,
+  SURFACEFLINGER = 1,
+  HWUI = 2,
+  GAME = 3,
+  APP = 4,
+};
+
 /**
  * Sends performance hints to inform the hint session of changes in the workload.
  *
@@ -83,14 +93,26 @@
  * @return 0 on success
  *         EPIPE if communication with the system service has failed.
  */
-int APerformanceHint_sendHint(void* session, SessionHint hint);
+int APerformanceHint_sendHint(APerformanceHintSession* session, SessionHint hint);
 
 /**
  * Return the list of thread ids, this API should only be used for testing only.
  */
-int APerformanceHint_getThreadIds(void* aPerformanceHintSession,
+int APerformanceHint_getThreadIds(APerformanceHintSession* session,
                                   int32_t* const threadIds, size_t* const size);
 
+/**
+ * Creates a session with additional options
+ */
+APerformanceHintSession* APerformanceHint_createSessionInternal(APerformanceHintManager* manager,
+                                        const int32_t* threadIds, size_t size,
+                                        int64_t initialTargetWorkDurationNanos, SessionTag tag);
+
+/**
+ * Forces FMQ to be enabled or disabled, for testing only.
+ */
+void APerformanceHint_setUseFMQForTesting(bool enabled);
+
 __END_DECLS
 
 #endif // ANDROID_PRIVATE_NATIVE_PERFORMANCE_HINT_PRIVATE_H
diff --git a/libs/adbd_auth/adbd_auth.cpp b/libs/adbd_auth/adbd_auth.cpp
index 78896ed..d31cb3d 100644
--- a/libs/adbd_auth/adbd_auth.cpp
+++ b/libs/adbd_auth/adbd_auth.cpp
@@ -390,13 +390,16 @@
         }
     }
 
-    static constexpr const char* key_paths[] = {"/adb_keys", "/data/misc/adb/adb_keys"};
+    static constexpr std::pair<const char*, bool> key_paths[] = {
+        {"/adb_keys",               true  /* follow symlinks */       },
+        {"/data/misc/adb/adb_keys", false /* don't follow symlinks */ },
+    };
     void IteratePublicKeys(bool (*callback)(void*, const char*, size_t), void* opaque) {
-        for (const auto& path : key_paths) {
+        for (const auto& [path, follow_symlinks] : key_paths) {
             if (access(path, R_OK) == 0) {
                 LOG(INFO) << "adbd_auth: loading keys from " << path;
                 std::string content;
-                if (!android::base::ReadFileToString(path, &content)) {
+                if (!android::base::ReadFileToString(path, &content, follow_symlinks)) {
                     PLOG(ERROR) << "adbd_auth: couldn't read " << path;
                     continue;
                 }
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 319716e..cbba711 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -40,6 +40,7 @@
 
 cc_library_headers {
     name: "libarect_headers",
+    host_supported: true,
     vendor_available: true,
     min_sdk_version: "29",
     // TODO(b/153609531): remove when no longer needed.
diff --git a/libs/attestation/OWNERS b/libs/attestation/OWNERS
index 4dbb0ea..76811f2 100644
--- a/libs/attestation/OWNERS
+++ b/libs/attestation/OWNERS
@@ -1,2 +1 @@
-chaviw@google.com
-svv@google.com
\ No newline at end of file
+svv@google.com
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 7da8d51..04b7186 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -62,6 +62,12 @@
 
     void setState(state_t state, time_t timestamp);
 
+    /**
+     * Copies the current state and accumulated times-in-state from the source. Resets
+     * the accumulated value.
+     */
+    void copyStatesFrom(const MultiStateCounter<T>& source);
+
     void setValue(state_t state, const T& value);
 
     /**
@@ -193,6 +199,22 @@
 }
 
 template <class T>
+void MultiStateCounter<T>::copyStatesFrom(const MultiStateCounter<T>& source) {
+    if (stateCount != source.stateCount) {
+        ALOGE("State count mismatch: %u vs. %u\n", stateCount, source.stateCount);
+        return;
+    }
+
+    currentState = source.currentState;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = source.states[i].timeInStateSinceUpdate;
+        states[i].counter = emptyValue;
+    }
+    lastStateChangeTimestamp = source.lastStateChangeTimestamp;
+    lastUpdateTimestamp = source.lastUpdateTimestamp;
+}
+
+template <class T>
 void MultiStateCounter<T>::setValue(state_t state, const T& value) {
     states[state].counter = value;
 }
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index cb11a54..a51a38a 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -72,6 +72,22 @@
     EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
 }
 
+TEST_F(MultiStateCounterTest, copyStatesFrom) {
+    DoubleMultiStateCounter sourceCounter(3, 0);
+
+    sourceCounter.updateValue(0, 0);
+    sourceCounter.setState(1, 0);
+    sourceCounter.setState(2, 1000);
+
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.copyStatesFrom(sourceCounter);
+    testCounter.updateValue(6.0, 3000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
+}
+
 TEST_F(MultiStateCounterTest, setEnabled) {
     DoubleMultiStateCounter testCounter(3, 0);
     testCounter.updateValue(0, 0);
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index de331b7..aac369d 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -87,6 +87,11 @@
 
 cc_cmake_snapshot {
     name: "binder_sdk",
+    dist: {
+        targets: ["binder_sdk"],
+        dest: "binder_sdk.zip",
+    },
+
     modules_host: [
         "libbinder_sdk",
         "libbinder_sdk_single_threaded",
@@ -451,6 +456,28 @@
 }
 
 soong_config_module_type {
+    name: "libbinder_remove_cache_static_list_config",
+    module_type: "cc_defaults",
+    config_namespace: "libbinder",
+    bool_variables: ["release_libbinder_remove_cache_static_list"],
+    properties: [
+        "cflags",
+    ],
+}
+
+libbinder_remove_cache_static_list_config {
+    name: "libbinder_remove_cache_static_list_flag",
+    soong_config_variables: {
+        release_libbinder_remove_cache_static_list: {
+            cflags: ["-DLIBBINDER_REMOVE_CACHE_STATIC_LIST"],
+            conditions_default: {
+                cflags: ["-DNO_LIBBINDER_REMOVE_CACHE_STATIC_LIST"],
+            },
+        },
+    },
+}
+
+soong_config_module_type {
     name: "libbinder_client_cache_config",
     module_type: "cc_defaults",
     config_namespace: "libbinder",
@@ -472,9 +499,35 @@
     },
 }
 
+soong_config_module_type {
+    name: "libbinder_addservice_cache_config",
+    module_type: "cc_defaults",
+    config_namespace: "libbinder",
+    bool_variables: ["release_libbinder_addservice_cache"],
+    properties: [
+        "cflags",
+    ],
+}
+
+libbinder_addservice_cache_config {
+    name: "libbinder_addservice_cache_flag",
+    soong_config_variables: {
+        release_libbinder_addservice_cache: {
+            cflags: ["-DLIBBINDER_ADDSERVICE_CACHE"],
+            conditions_default: {
+                cflags: ["-DNO_LIBBINDER_ADDSERVICE_CACHE"],
+            },
+        },
+    },
+}
+
 cc_defaults {
     name: "libbinder_kernel_defaults",
-    defaults: ["libbinder_client_cache_flag"],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+        "libbinder_remove_cache_static_list_flag",
+    ],
     srcs: [
         "BufferedTextOutput.cpp",
         "BackendUnifiedServiceManager.cpp",
@@ -484,6 +537,7 @@
         "ProcessState.cpp",
         "Static.cpp",
         ":libbinder_aidl",
+        ":libbinder_accessor_aidl",
         ":libbinder_device_interface_sources",
     ],
     target: {
@@ -795,8 +849,8 @@
         "aidl/android/os/IServiceCallback.aidl",
         "aidl/android/os/IServiceManager.aidl",
         "aidl/android/os/Service.aidl",
+        "aidl/android/os/ServiceWithMetadata.aidl",
         "aidl/android/os/ServiceDebugInfo.aidl",
-        ":libbinder_accessor_aidl",
     ],
     path: "aidl",
 }
@@ -807,26 +861,7 @@
         "aidl/android/os/IAccessor.aidl",
     ],
     path: "aidl",
-}
-
-// TODO(b/353492849): Make this interface private to libbinder.
-aidl_interface {
-    name: "android.os.accessor",
-    srcs: [":libbinder_accessor_aidl"],
-    unstable: true,
-    backend: {
-        rust: {
-            enabled: true,
-            apex_available: [
-                "com.android.virt",
-            ],
-        },
-    },
-    visibility: [
-        ":__subpackages__",
-        "//system/tools/aidl:__subpackages__",
-        "//packages/modules/Virtualization:__subpackages__",
-    ],
+    visibility: [":__subpackages__"],
 }
 
 aidl_interface {
@@ -843,6 +878,7 @@
     backend: {
         rust: {
             apex_available: [
+                "//apex_available:platform",
                 "com.android.virt",
             ],
             enabled: true,
@@ -885,13 +921,16 @@
         symbol_file: "libbinder_rpc_unstable.map.txt",
     },
 
+    header_abi_checker: {
+        enabled: false,
+    },
+
     // This library is intentionally limited to these targets, and it will be removed later.
     // Do not expand the visibility.
     visibility: [
         ":__subpackages__",
         "//packages/modules/Virtualization:__subpackages__",
         "//device/google/cuttlefish/shared/minidroid:__subpackages__",
-        "//system/software_defined_vehicle:__subpackages__",
         "//visibility:any_system_partition",
     ],
 }
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 54f687b..7c0319a 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -15,7 +15,9 @@
  */
 #include "BackendUnifiedServiceManager.h"
 
+#include <android-base/strings.h>
 #include <android/os/IAccessor.h>
+#include <android/os/IServiceManager.h>
 #include <binder/RpcSession.h>
 
 #if defined(__BIONIC__) && !defined(__ANDROID_VNDK__)
@@ -24,57 +26,289 @@
 
 namespace android {
 
+#ifdef LIBBINDER_CLIENT_CACHE
+constexpr bool kUseCache = true;
+#else
+constexpr bool kUseCache = false;
+#endif
+
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
+#ifdef LIBBINDER_REMOVE_CACHE_STATIC_LIST
+constexpr bool kRemoveStaticList = true;
+#else
+constexpr bool kRemoveStaticList = false;
+#endif
+
 using AidlServiceManager = android::os::IServiceManager;
-using IAccessor = android::os::IAccessor;
+using android::os::IAccessor;
+using binder::Status;
+
+static const char* kUnsupportedOpNoServiceManager =
+        "Unsupported operation without a kernel binder servicemanager process";
+
+static const char* kStaticCachableList[] = {
+        // go/keep-sorted start
+        "accessibility",
+        "account",
+        "activity",
+        "alarm",
+        "android.frameworks.stats.IStats/default",
+        "android.system.keystore2.IKeystoreService/default",
+        "appops",
+        "audio",
+        "autofill",
+        "batteryproperties",
+        "batterystats",
+        "biometic",
+        "carrier_config",
+        "connectivity",
+        "content",
+        "content_capture",
+        "device_policy",
+        "display",
+        "dropbox",
+        "econtroller",
+        "graphicsstats",
+        "input",
+        "input_method",
+        "isub",
+        "jobscheduler",
+        "legacy_permission",
+        "location",
+        "lock_settings",
+        "media.extractor",
+        "media.metrics",
+        "media.player",
+        "media.resource_manager",
+        "media_resource_monitor",
+        "mount",
+        "netd_listener",
+        "netstats",
+        "network_management",
+        "nfc",
+        "notification",
+        "package",
+        "package_native",
+        "performance_hint",
+        "permission",
+        "permission_checker",
+        "permissionmgr",
+        "phone",
+        "platform_compat",
+        "power",
+        "processinfo",
+        "role",
+        "sensitive_content_protection_service",
+        "sensorservice",
+        "statscompanion",
+        "telephony.registry",
+        "thermalservice",
+        "time_detector",
+        "tracing.proxy",
+        "trust",
+        "uimode",
+        "user",
+        "vibrator",
+        "virtualdevice",
+        "virtualdevice_native",
+        "webviewupdate",
+        "window",
+        // go/keep-sorted end
+};
+
+os::ServiceWithMetadata createServiceWithMetadata(const sp<IBinder>& service, bool isLazyService) {
+    os::ServiceWithMetadata out = os::ServiceWithMetadata();
+    out.service = service;
+    out.isLazyService = isLazyService;
+    return out;
+}
+
+bool BinderCacheWithInvalidation::isClientSideCachingEnabled(const std::string& serviceName) {
+    sp<ProcessState> self = ProcessState::selfOrNull();
+    if (!self || self->getThreadPoolMaxTotalThreadCount() <= 0) {
+        ALOGW("Thread Pool max thread count is 0. Cannot cache binder as linkToDeath cannot be "
+              "implemented. serviceName: %s",
+              serviceName.c_str());
+        return false;
+    }
+    if (kRemoveStaticList) return true;
+    for (const char* name : kStaticCachableList) {
+        if (name == serviceName) {
+            return true;
+        }
+    }
+    return false;
+}
+
+Status BackendUnifiedServiceManager::updateCache(const std::string& serviceName,
+                                                 const os::Service& service) {
+    if (!kUseCache) {
+        return Status::ok();
+    }
+
+    if (service.getTag() == os::Service::Tag::serviceWithMetadata) {
+        auto serviceWithMetadata = service.get<os::Service::Tag::serviceWithMetadata>();
+        return updateCache(serviceName, serviceWithMetadata.service,
+                           serviceWithMetadata.isLazyService);
+    }
+    return Status::ok();
+}
+
+Status BackendUnifiedServiceManager::updateCache(const std::string& serviceName,
+                                                 const sp<IBinder>& binder, bool isServiceLazy) {
+    std::string traceStr;
+    // Don't cache if service is lazy
+    if (kRemoveStaticList && isServiceLazy) {
+        return Status::ok();
+    }
+    if (atrace_is_tag_enabled(ATRACE_TAG_AIDL)) {
+        traceStr = "BinderCacheWithInvalidation::updateCache : " + serviceName;
+    }
+    binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL, traceStr.c_str());
+    if (!binder) {
+        binder::ScopedTrace
+                aidlTrace(ATRACE_TAG_AIDL,
+                          "BinderCacheWithInvalidation::updateCache failed: binder_null");
+    } else if (!binder->isBinderAlive()) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "isBinderAlive_false");
+    }
+    // If we reach here with kRemoveStaticList=true then we know service isn't lazy
+    else if (mCacheForGetService->isClientSideCachingEnabled(serviceName)) {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache successful");
+        return mCacheForGetService->setItem(serviceName, binder);
+    } else {
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::updateCache failed: "
+                                      "caching_not_enabled");
+    }
+    return Status::ok();
+}
+
+bool BackendUnifiedServiceManager::returnIfCached(const std::string& serviceName,
+                                                  os::Service* _out) {
+    if (!kUseCache) {
+        return false;
+    }
+    sp<IBinder> item = mCacheForGetService->getItem(serviceName);
+    // TODO(b/363177618): Enable caching for binders which are always null.
+    if (item != nullptr && item->isBinderAlive()) {
+        *_out = createServiceWithMetadata(item, false);
+        return true;
+    }
+    return false;
+}
 
 BackendUnifiedServiceManager::BackendUnifiedServiceManager(const sp<AidlServiceManager>& impl)
-      : mTheRealServiceManager(impl) {}
-
-sp<AidlServiceManager> BackendUnifiedServiceManager::getImpl() {
-    return mTheRealServiceManager;
+      : mTheRealServiceManager(impl) {
+    mCacheForGetService = std::make_shared<BinderCacheWithInvalidation>();
 }
 
-binder::Status BackendUnifiedServiceManager::getService(const ::std::string& name,
-                                                        sp<IBinder>* _aidl_return) {
+Status BackendUnifiedServiceManager::getService(const ::std::string& name,
+                                                sp<IBinder>* _aidl_return) {
     os::Service service;
-    binder::Status status = getService2(name, &service);
-    *_aidl_return = service.get<os::Service::Tag::binder>();
+    Status status = getService2(name, &service);
+    if (status.isOk()) {
+        *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    }
     return status;
 }
 
-binder::Status BackendUnifiedServiceManager::getService2(const ::std::string& name,
-                                                         os::Service* _out) {
+Status BackendUnifiedServiceManager::getService2(const ::std::string& name, os::Service* _out) {
+    if (returnIfCached(name, _out)) {
+        return Status::ok();
+    }
     os::Service service;
-    binder::Status status = mTheRealServiceManager->getService2(name, &service);
-    toBinderService(service, _out);
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->getService2(name, &service);
+    }
+
+    if (status.isOk()) {
+        status = toBinderService(name, service, _out);
+        if (status.isOk()) {
+            return updateCache(name, service);
+        }
+    }
     return status;
 }
 
-binder::Status BackendUnifiedServiceManager::checkService(const ::std::string& name,
-                                                          os::Service* _out) {
+Status BackendUnifiedServiceManager::checkService(const ::std::string& name,
+                                                  sp<IBinder>* _aidl_return) {
     os::Service service;
-    binder::Status status = mTheRealServiceManager->checkService(name, &service);
-    toBinderService(service, _out);
+    Status status = checkService2(name, &service);
+    if (status.isOk()) {
+        *_aidl_return = service.get<os::Service::Tag::serviceWithMetadata>().service;
+    }
     return status;
 }
 
-void BackendUnifiedServiceManager::toBinderService(const os::Service& in, os::Service* _out) {
+Status BackendUnifiedServiceManager::checkService2(const ::std::string& name, os::Service* _out) {
+    os::Service service;
+    if (returnIfCached(name, _out)) {
+        return Status::ok();
+    }
+
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->checkService2(name, &service);
+    }
+    if (status.isOk()) {
+        status = toBinderService(name, service, _out);
+        if (status.isOk()) {
+            return updateCache(name, service);
+        }
+    }
+    return status;
+}
+
+Status BackendUnifiedServiceManager::toBinderService(const ::std::string& name,
+                                                     const os::Service& in, os::Service* _out) {
     switch (in.getTag()) {
-        case os::Service::Tag::binder: {
+        case os::Service::Tag::serviceWithMetadata: {
+            auto serviceWithMetadata = in.get<os::Service::Tag::serviceWithMetadata>();
+            if (serviceWithMetadata.service == nullptr) {
+                // failed to find a service. Check to see if we have any local
+                // injected Accessors for this service.
+                os::Service accessor;
+                Status status = getInjectedAccessor(name, &accessor);
+                if (!status.isOk()) {
+                    *_out = os::Service::make<os::Service::Tag::serviceWithMetadata>(
+                            createServiceWithMetadata(nullptr, false));
+                    return status;
+                }
+                if (accessor.getTag() == os::Service::Tag::accessor &&
+                    accessor.get<os::Service::Tag::accessor>() != nullptr) {
+                    ALOGI("Found local injected service for %s, will attempt to create connection",
+                          name.c_str());
+                    // Call this again using the accessor Service to get the real
+                    // service's binder into _out
+                    return toBinderService(name, accessor, _out);
+                }
+            }
+
             *_out = in;
-            break;
+            return Status::ok();
         }
         case os::Service::Tag::accessor: {
             sp<IBinder> accessorBinder = in.get<os::Service::Tag::accessor>();
             sp<IAccessor> accessor = interface_cast<IAccessor>(accessorBinder);
             if (accessor == nullptr) {
                 ALOGE("Service#accessor doesn't have accessor. VM is maybe starting...");
-                *_out = os::Service::make<os::Service::Tag::binder>(nullptr);
-                break;
+                *_out = os::Service::make<os::Service::Tag::serviceWithMetadata>(
+                        createServiceWithMetadata(nullptr, false));
+                return Status::ok();
             }
             auto request = [=] {
                 os::ParcelFileDescriptor fd;
-                binder::Status ret = accessor->addConnection(&fd);
+                Status ret = accessor->addConnection(&fd);
                 if (ret.isOk()) {
                     return base::unique_fd(fd.release());
                 } else {
@@ -83,10 +317,16 @@
                 }
             };
             auto session = RpcSession::make();
-            session->setupPreconnectedClient(base::unique_fd{}, request);
+            status_t status = session->setupPreconnectedClient(base::unique_fd{}, request);
+            if (status != OK) {
+                ALOGE("Failed to set up preconnected binder RPC client: %s",
+                      statusToString(status).c_str());
+                return Status::fromStatusT(status);
+            }
             session->setSessionSpecificRoot(accessorBinder);
-            *_out = os::Service::make<os::Service::Tag::binder>(session->getRootObject());
-            break;
+            *_out = os::Service::make<os::Service::Tag::serviceWithMetadata>(
+                    createServiceWithMetadata(session->getRootObject(), false));
+            return Status::ok();
         }
         default: {
             LOG_ALWAYS_FATAL("Unknown service type: %d", in.getTag());
@@ -94,64 +334,159 @@
     }
 }
 
-binder::Status BackendUnifiedServiceManager::addService(const ::std::string& name,
-                                                        const sp<IBinder>& service,
-                                                        bool allowIsolated, int32_t dumpPriority) {
-    return mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+Status BackendUnifiedServiceManager::addService(const ::std::string& name,
+                                                const sp<IBinder>& service, bool allowIsolated,
+                                                int32_t dumpPriority) {
+    if (mTheRealServiceManager) {
+        Status status =
+                mTheRealServiceManager->addService(name, service, allowIsolated, dumpPriority);
+        // mEnableAddServiceCache is true by default.
+        if (kUseCacheInAddService && mEnableAddServiceCache && status.isOk()) {
+            return updateCache(name, service,
+                               dumpPriority & android::os::IServiceManager::FLAG_IS_LAZY_SERVICE);
+        }
+        return status;
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::listServices(
-        int32_t dumpPriority, ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->listServices(dumpPriority, _aidl_return);
+Status BackendUnifiedServiceManager::listServices(int32_t dumpPriority,
+                                                  ::std::vector<::std::string>* _aidl_return) {
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->listServices(dumpPriority, _aidl_return);
+    }
+    if (!status.isOk()) return status;
+
+    appendInjectedAccessorServices(_aidl_return);
+
+    return status;
 }
-binder::Status BackendUnifiedServiceManager::registerForNotifications(
+Status BackendUnifiedServiceManager::registerForNotifications(
         const ::std::string& name, const sp<os::IServiceCallback>& callback) {
-    return mTheRealServiceManager->registerForNotifications(name, callback);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->registerForNotifications(name, callback);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::unregisterForNotifications(
+Status BackendUnifiedServiceManager::unregisterForNotifications(
         const ::std::string& name, const sp<os::IServiceCallback>& callback) {
-    return mTheRealServiceManager->unregisterForNotifications(name, callback);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->unregisterForNotifications(name, callback);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::isDeclared(const ::std::string& name,
-                                                        bool* _aidl_return) {
-    return mTheRealServiceManager->isDeclared(name, _aidl_return);
+Status BackendUnifiedServiceManager::isDeclared(const ::std::string& name, bool* _aidl_return) {
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->isDeclared(name, _aidl_return);
+    }
+    if (!status.isOk()) return status;
+
+    if (!*_aidl_return) {
+        forEachInjectedAccessorService([&](const std::string& instance) {
+            if (name == instance) {
+                *_aidl_return = true;
+            }
+        });
+    }
+
+    return status;
 }
-binder::Status BackendUnifiedServiceManager::getDeclaredInstances(
+Status BackendUnifiedServiceManager::getDeclaredInstances(
         const ::std::string& iface, ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->getDeclaredInstances(iface, _aidl_return);
+    Status status = Status::ok();
+    if (mTheRealServiceManager) {
+        status = mTheRealServiceManager->getDeclaredInstances(iface, _aidl_return);
+    }
+    if (!status.isOk()) return status;
+
+    forEachInjectedAccessorService([&](const std::string& instance) {
+        // Declared instances have the format
+        // <interface>/instance like foo.bar.ISomething/instance
+        // If it does not have that format, consider the instance to be ""
+        std::string_view name(instance);
+        if (base::ConsumePrefix(&name, iface + "/")) {
+            _aidl_return->emplace_back(name);
+        } else if (iface == instance) {
+            _aidl_return->push_back("");
+        }
+    });
+
+    return status;
 }
-binder::Status BackendUnifiedServiceManager::updatableViaApex(
+Status BackendUnifiedServiceManager::updatableViaApex(
         const ::std::string& name, ::std::optional<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->updatableViaApex(name, _aidl_return);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->updatableViaApex(name, _aidl_return);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::getUpdatableNames(
-        const ::std::string& apexName, ::std::vector<::std::string>* _aidl_return) {
-    return mTheRealServiceManager->getUpdatableNames(apexName, _aidl_return);
+Status BackendUnifiedServiceManager::getUpdatableNames(const ::std::string& apexName,
+                                                       ::std::vector<::std::string>* _aidl_return) {
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->getUpdatableNames(apexName, _aidl_return);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::getConnectionInfo(
+Status BackendUnifiedServiceManager::getConnectionInfo(
         const ::std::string& name, ::std::optional<os::ConnectionInfo>* _aidl_return) {
-    return mTheRealServiceManager->getConnectionInfo(name, _aidl_return);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->getConnectionInfo(name, _aidl_return);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::registerClientCallback(
+Status BackendUnifiedServiceManager::registerClientCallback(
         const ::std::string& name, const sp<IBinder>& service,
         const sp<os::IClientCallback>& callback) {
-    return mTheRealServiceManager->registerClientCallback(name, service, callback);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->registerClientCallback(name, service, callback);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::tryUnregisterService(const ::std::string& name,
-                                                                  const sp<IBinder>& service) {
-    return mTheRealServiceManager->tryUnregisterService(name, service);
+Status BackendUnifiedServiceManager::tryUnregisterService(const ::std::string& name,
+                                                          const sp<IBinder>& service) {
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->tryUnregisterService(name, service);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
-binder::Status BackendUnifiedServiceManager::getServiceDebugInfo(
+Status BackendUnifiedServiceManager::getServiceDebugInfo(
         ::std::vector<os::ServiceDebugInfo>* _aidl_return) {
-    return mTheRealServiceManager->getServiceDebugInfo(_aidl_return);
+    if (mTheRealServiceManager) {
+        return mTheRealServiceManager->getServiceDebugInfo(_aidl_return);
+    }
+    return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+                                     kUnsupportedOpNoServiceManager);
 }
 
 [[clang::no_destroy]] static std::once_flag gUSmOnce;
 [[clang::no_destroy]] static sp<BackendUnifiedServiceManager> gUnifiedServiceManager;
 
+static bool hasOutOfProcessServiceManager() {
+#ifndef BINDER_WITH_KERNEL_IPC
+    return false;
+#else
+#if defined(__BIONIC__) && !defined(__ANDROID_VNDK__)
+    return android::base::GetBoolProperty("servicemanager.installed", true);
+#else
+    return true;
+#endif
+#endif // BINDER_WITH_KERNEL_IPC
+}
+
 sp<BackendUnifiedServiceManager> getBackendUnifiedServiceManager() {
     std::call_once(gUSmOnce, []() {
 #if defined(__BIONIC__) && !defined(__ANDROID_VNDK__)
-        /* wait for service manager */ {
+        /* wait for service manager */
+        if (hasOutOfProcessServiceManager()) {
             using std::literals::chrono_literals::operator""s;
             using android::base::WaitForProperty;
             while (!WaitForProperty("servicemanager.ready", "true", 1s)) {
@@ -161,7 +496,7 @@
 #endif
 
         sp<AidlServiceManager> sm = nullptr;
-        while (sm == nullptr) {
+        while (hasOutOfProcessServiceManager() && sm == nullptr) {
             sm = interface_cast<AidlServiceManager>(
                     ProcessState::self()->getContextObject(nullptr));
             if (sm == nullptr) {
@@ -177,4 +512,4 @@
     return gUnifiedServiceManager;
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/binder/BackendUnifiedServiceManager.h b/libs/binder/BackendUnifiedServiceManager.h
index f5d7e66..c14f280 100644
--- a/libs/binder/BackendUnifiedServiceManager.h
+++ b/libs/binder/BackendUnifiedServiceManager.h
@@ -18,17 +18,112 @@
 #include <android/os/BnServiceManager.h>
 #include <android/os/IServiceManager.h>
 #include <binder/IPCThreadState.h>
+#include <binder/Trace.h>
+#include <map>
+#include <memory>
 
 namespace android {
 
+class BinderCacheWithInvalidation
+      : public std::enable_shared_from_this<BinderCacheWithInvalidation> {
+    class BinderInvalidation : public IBinder::DeathRecipient {
+    public:
+        BinderInvalidation(std::weak_ptr<BinderCacheWithInvalidation> cache, const std::string& key)
+              : mCache(cache), mKey(key) {}
+
+        void binderDied(const wp<IBinder>& who) override {
+            sp<IBinder> binder = who.promote();
+            if (std::shared_ptr<BinderCacheWithInvalidation> cache = mCache.lock()) {
+                cache->removeItem(mKey, binder);
+            } else {
+                ALOGI("Binder Cache pointer expired: %s", mKey.c_str());
+            }
+        }
+
+    private:
+        std::weak_ptr<BinderCacheWithInvalidation> mCache;
+        std::string mKey;
+    };
+    struct Entry {
+        sp<IBinder> service;
+        sp<BinderInvalidation> deathRecipient;
+    };
+
+public:
+    sp<IBinder> getItem(const std::string& key) const {
+        std::lock_guard<std::mutex> lock(mCacheMutex);
+
+        if (auto it = mCache.find(key); it != mCache.end()) {
+            return it->second.service;
+        }
+        return nullptr;
+    }
+
+    bool removeItem(const std::string& key, const sp<IBinder>& who) {
+        std::string traceStr;
+        uint64_t tag = ATRACE_TAG_AIDL;
+        if (atrace_is_tag_enabled(tag)) {
+            traceStr = "BinderCacheWithInvalidation::removeItem " + key;
+        }
+        binder::ScopedTrace aidlTrace(tag, traceStr.c_str());
+        std::lock_guard<std::mutex> lock(mCacheMutex);
+        if (auto it = mCache.find(key); it != mCache.end()) {
+            if (it->second.service == who) {
+                status_t result = who->unlinkToDeath(it->second.deathRecipient);
+                if (result != DEAD_OBJECT) {
+                    ALOGW("Unlinking to dead binder resulted in: %d", result);
+                }
+                mCache.erase(key);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    binder::Status setItem(const std::string& key, const sp<IBinder>& item) {
+        sp<BinderInvalidation> deathRecipient =
+                sp<BinderInvalidation>::make(shared_from_this(), key);
+
+        // linkToDeath if binder is a remote binder.
+        if (item->localBinder() == nullptr) {
+            status_t status = item->linkToDeath(deathRecipient);
+            if (status != android::OK) {
+                std::string traceStr;
+                uint64_t tag = ATRACE_TAG_AIDL;
+                if (atrace_is_tag_enabled(tag)) {
+                    traceStr =
+                            "BinderCacheWithInvalidation::setItem Failed LinkToDeath for service " +
+                            key + " : " + std::to_string(status);
+                }
+                binder::ScopedTrace aidlTrace(tag, traceStr.c_str());
+
+                ALOGE("Failed to linkToDeath binder for service %s. Error: %d", key.c_str(),
+                      status);
+                return binder::Status::fromStatusT(status);
+            }
+        }
+        binder::ScopedTrace aidlTrace(ATRACE_TAG_AIDL,
+                                      "BinderCacheWithInvalidation::setItem Successfully Cached");
+        std::lock_guard<std::mutex> lock(mCacheMutex);
+        mCache[key] = {.service = item, .deathRecipient = deathRecipient};
+        return binder::Status::ok();
+    }
+
+    bool isClientSideCachingEnabled(const std::string& serviceName);
+
+private:
+    std::map<std::string, Entry> mCache;
+    mutable std::mutex mCacheMutex;
+};
+
 class BackendUnifiedServiceManager : public android::os::BnServiceManager {
 public:
     explicit BackendUnifiedServiceManager(const sp<os::IServiceManager>& impl);
 
-    sp<os::IServiceManager> getImpl();
     binder::Status getService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
     binder::Status getService2(const ::std::string& name, os::Service* out) override;
-    binder::Status checkService(const ::std::string& name, os::Service* out) override;
+    binder::Status checkService(const ::std::string& name, sp<IBinder>* _aidl_return) override;
+    binder::Status checkService2(const ::std::string& name, os::Service* out) override;
     binder::Status addService(const ::std::string& name, const sp<IBinder>& service,
                               bool allowIsolated, int32_t dumpPriority) override;
     binder::Status listServices(int32_t dumpPriority,
@@ -52,18 +147,30 @@
                                         const sp<IBinder>& service) override;
     binder::Status getServiceDebugInfo(::std::vector<os::ServiceDebugInfo>* _aidl_return) override;
 
+    void enableAddServiceCache(bool value) { mEnableAddServiceCache = value; }
     // for legacy ABI
     const String16& getInterfaceDescriptor() const override {
         return mTheRealServiceManager->getInterfaceDescriptor();
     }
 
-    IBinder* onAsBinder() override { return IInterface::asBinder(mTheRealServiceManager).get(); }
-
 private:
+    bool mEnableAddServiceCache = true;
+    std::shared_ptr<BinderCacheWithInvalidation> mCacheForGetService;
     sp<os::IServiceManager> mTheRealServiceManager;
-    void toBinderService(const os::Service& in, os::Service* _out);
+    binder::Status toBinderService(const ::std::string& name, const os::Service& in,
+                                   os::Service* _out);
+    binder::Status updateCache(const std::string& serviceName, const os::Service& service);
+    binder::Status updateCache(const std::string& serviceName, const sp<IBinder>& binder,
+                               bool isLazyService);
+    bool returnIfCached(const std::string& serviceName, os::Service* _out);
 };
 
 sp<BackendUnifiedServiceManager> getBackendUnifiedServiceManager();
 
-} // namespace android
\ No newline at end of file
+android::binder::Status getInjectedAccessor(const std::string& name, android::os::Service* service);
+void appendInjectedAccessorServices(std::vector<std::string>* list);
+// Do not call any other service manager APIs that might take the accessor
+// mutex because this will be holding it!
+void forEachInjectedAccessorService(const std::function<void(const std::string&)>& f);
+
+} // namespace android
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index c57c9cd..bc7ae37 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -38,6 +38,7 @@
 #endif
 
 #include "BuildFlags.h"
+#include "Constants.h"
 #include "OS.h"
 #include "RpcState.h"
 
@@ -70,8 +71,6 @@
 constexpr bool kEnableRecording = false;
 #endif
 
-// Log any reply transactions for which the data exceeds this size
-#define LOG_REPLIES_OVER_SIZE (300 * 1024)
 // ---------------------------------------------------------------------------
 
 IBinder::IBinder()
@@ -143,6 +142,22 @@
     return reply.readNullableStrongBinder(out);
 }
 
+status_t IBinder::addFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    BpBinder* proxy = this->remoteBinder();
+    if (proxy != nullptr) {
+        return proxy->addFrozenStateChangeCallback(callback);
+    }
+    return INVALID_OPERATION;
+}
+
+status_t IBinder::removeFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    BpBinder* proxy = this->remoteBinder();
+    if (proxy != nullptr) {
+        return proxy->removeFrozenStateChangeCallback(callback);
+    }
+    return INVALID_OPERATION;
+}
+
 status_t IBinder::getDebugPid(pid_t* out) {
     BBinder* local = this->localBinder();
     if (local != nullptr) {
@@ -272,7 +287,7 @@
     // for below objects
     RpcMutex mLock;
     std::set<sp<RpcServerLink>> mRpcServerLinks;
-    BpBinder::ObjectManager mObjects;
+    BpBinder::ObjectManager mObjectMgr;
 
     unique_fd mRecordingFd;
 };
@@ -396,7 +411,7 @@
     // In case this is being transacted on in the same process.
     if (reply != nullptr) {
         reply->setDataPosition(0);
-        if (reply->dataSize() > LOG_REPLIES_OVER_SIZE) {
+        if (reply->dataSize() > binder::kLogTransactionsOverBytes) {
             ALOGW("Large reply transaction of %zu bytes, interface descriptor %s, code %d",
                   reply->dataSize(), String8(getInterfaceDescriptor()).c_str(), code);
         }
@@ -452,7 +467,7 @@
     LOG_ALWAYS_FATAL_IF(!e, "no memory");
 
     RpcMutexUniqueLock _l(e->mLock);
-    return e->mObjects.attach(objectID, object, cleanupCookie, func);
+    return e->mObjectMgr.attach(objectID, object, cleanupCookie, func);
 }
 
 void* BBinder::findObject(const void* objectID) const
@@ -461,7 +476,7 @@
     if (!e) return nullptr;
 
     RpcMutexUniqueLock _l(e->mLock);
-    return e->mObjects.find(objectID);
+    return e->mObjectMgr.find(objectID);
 }
 
 void* BBinder::detachObject(const void* objectID) {
@@ -469,7 +484,7 @@
     if (!e) return nullptr;
 
     RpcMutexUniqueLock _l(e->mLock);
-    return e->mObjects.detach(objectID);
+    return e->mObjectMgr.detach(objectID);
 }
 
 void BBinder::withLock(const std::function<void()>& doWithLock) {
@@ -485,7 +500,7 @@
     Extras* e = getOrCreateExtras();
     LOG_ALWAYS_FATAL_IF(!e, "no memory");
     RpcMutexUniqueLock _l(e->mLock);
-    return e->mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
+    return e->mObjectMgr.lookupOrCreateWeak(objectID, make, makeArgs);
 }
 
 BBinder* BBinder::localBinder()
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 6594aa6..c13e0f9 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -28,6 +28,7 @@
 #include <stdio.h>
 
 #include "BuildFlags.h"
+#include "Constants.h"
 #include "file.h"
 
 //#undef ALOGV
@@ -63,9 +64,6 @@
 
 static constexpr uint32_t kBinderProxyCountWarnInterval = 5000;
 
-// Log any transactions for which the data exceeds this size
-#define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024)
-
 enum {
     LIMIT_REACHED_MASK = 0x80000000,        // A flag denoting that the limit has been reached
     WARNING_REACHED_MASK = 0x40000000,      // A flag denoting that the warning has been reached
@@ -78,7 +76,16 @@
 
 BpBinder::ObjectManager::~ObjectManager()
 {
-    kill();
+    const size_t N = mObjects.size();
+    ALOGV("Killing %zu objects in manager %p", N, this);
+    for (auto i : mObjects) {
+        const entry_t& e = i.second;
+        if (e.func != nullptr) {
+            e.func(i.first, e.object, e.cleanupCookie);
+        }
+    }
+
+    mObjects.clear();
 }
 
 void* BpBinder::ObjectManager::attach(const void* objectID, void* object, void* cleanupCookie,
@@ -144,27 +151,14 @@
     return newObj;
 }
 
-void BpBinder::ObjectManager::kill()
-{
-    const size_t N = mObjects.size();
-    ALOGV("Killing %zu objects in manager %p", N, this);
-    for (auto i : mObjects) {
-        const entry_t& e = i.second;
-        if (e.func != nullptr) {
-            e.func(i.first, e.object, e.cleanupCookie);
-        }
-    }
-
-    mObjects.clear();
-}
-
 // ---------------------------------------------------------------------------
 
-sp<BpBinder> BpBinder::create(int32_t handle) {
+sp<BpBinder> BpBinder::create(int32_t handle, std::function<void()>* postTask) {
     if constexpr (!kEnableKernelIpc) {
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
         return nullptr;
     }
+    LOG_ALWAYS_FATAL_IF(postTask == nullptr, "BAD STATE");
 
     int32_t trackedUid = -1;
     if (sCountByUidEnabled) {
@@ -183,7 +177,11 @@
                 ALOGE("Still too many binder proxy objects sent to uid %d from uid %d (%d proxies "
                       "held)",
                       getuid(), trackedUid, trackedValue);
-                if (sLimitCallback) sLimitCallback(trackedUid);
+
+                if (sLimitCallback) {
+                    *postTask = [=]() { sLimitCallback(trackedUid); };
+                }
+
                 sLastLimitCallbackMap[trackedUid] = trackedValue;
             }
         } else {
@@ -192,12 +190,18 @@
                     && currentValue < sBinderProxyCountHighWatermark
                     && ((trackedValue & WARNING_REACHED_MASK) == 0)) [[unlikely]] {
                 sTrackingMap[trackedUid] |= WARNING_REACHED_MASK;
-                if (sWarningCallback) sWarningCallback(trackedUid);
+                if (sWarningCallback) {
+                    *postTask = [=]() { sWarningCallback(trackedUid); };
+                }
             } else if (currentValue >= sBinderProxyCountHighWatermark) {
                 ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
                       getuid(), trackedUid, trackedValue);
                 sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK;
-                if (sLimitCallback) sLimitCallback(trackedUid);
+
+                if (sLimitCallback) {
+                    *postTask = [=]() { sLimitCallback(trackedUid); };
+                }
+
                 sLastLimitCallbackMap[trackedUid] = trackedValue & COUNTING_VALUE_MASK;
                 if (sBinderProxyThrottleCreate) {
                     ALOGI("Throttling binder proxy creates from uid %d in uid %d until binder proxy"
@@ -397,9 +401,11 @@
 
             status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
         }
-        if (data.dataSize() > LOG_TRANSACTIONS_OVER_SIZE) {
+
+        if (data.dataSize() > binder::kLogTransactionsOverBytes) {
             RpcMutexUniqueLock _l(mLock);
-            ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d",
+            ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d was "
+                  "sent",
                   data.dataSize(), String8(mDescriptorCache).c_str(), code);
         }
 
@@ -557,6 +563,123 @@
     }
 }
 
+status_t BpBinder::addFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(),
+                        "addFrozenStateChangeCallback() is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+    LOG_ALWAYS_FATAL_IF(ProcessState::self()->getThreadPoolMaxTotalThreadCount() == 0,
+                        "addFrozenStateChangeCallback on %s but there are no threads "
+                        "(yet?) listening to incoming transactions. See "
+                        "ProcessState::startThreadPool "
+                        "and ProcessState::setThreadPoolMaxThreadCount. Generally you should "
+                        "setup the binder threadpool before other initialization steps.",
+                        String8(getInterfaceDescriptor()).c_str());
+    LOG_ALWAYS_FATAL_IF(callback == nullptr,
+                        "addFrozenStateChangeCallback(): callback must be non-NULL");
+
+    const sp<FrozenStateChangeCallback> strongCallback = callback.promote();
+    if (strongCallback == nullptr) {
+        return BAD_VALUE;
+    }
+
+    {
+        RpcMutexUniqueLock _l(mLock);
+        if (!mFrozen) {
+            ALOGV("Requesting freeze notification: %p handle %d\n", this, binderHandle());
+            IPCThreadState* self = IPCThreadState::self();
+            status_t status = self->addFrozenStateChangeCallback(binderHandle(), this);
+            if (status != NO_ERROR) {
+                // Avoids logspam if kernel does not support freeze
+                // notification.
+                if (status != INVALID_OPERATION) {
+                    ALOGE("IPCThreadState.addFrozenStateChangeCallback "
+                          "failed with %s. %p handle %d\n",
+                          statusToString(status).c_str(), this, binderHandle());
+                }
+                return status;
+            }
+            mFrozen = std::make_unique<FrozenStateChange>();
+            if (!mFrozen) {
+                std::ignore =
+                        IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(),
+                                                                                this);
+                return NO_MEMORY;
+            }
+        }
+        if (mFrozen->initialStateReceived) {
+            strongCallback->onStateChanged(wp<BpBinder>::fromExisting(this),
+                                           mFrozen->isFrozen
+                                                   ? FrozenStateChangeCallback::State::FROZEN
+                                                   : FrozenStateChangeCallback::State::UNFROZEN);
+        }
+        ssize_t res = mFrozen->callbacks.add(callback);
+        if (res < 0) {
+            return res;
+        }
+        return NO_ERROR;
+    }
+}
+
+status_t BpBinder::removeFrozenStateChangeCallback(const wp<FrozenStateChangeCallback>& callback) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(),
+                        "removeFrozenStateChangeCallback() is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+
+    RpcMutexUniqueLock _l(mLock);
+
+    const size_t N = mFrozen ? mFrozen->callbacks.size() : 0;
+    for (size_t i = 0; i < N; i++) {
+        if (mFrozen->callbacks.itemAt(i) == callback) {
+            mFrozen->callbacks.removeAt(i);
+            if (mFrozen->callbacks.size() == 0) {
+                ALOGV("Clearing freeze notification: %p handle %d\n", this, binderHandle());
+                status_t status =
+                        IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(),
+                                                                                this);
+                if (status != NO_ERROR) {
+                    ALOGE("Unexpected error from "
+                          "IPCThreadState.removeFrozenStateChangeCallback: %s. "
+                          "%p handle %d\n",
+                          statusToString(status).c_str(), this, binderHandle());
+                }
+                mFrozen.reset();
+            }
+            return NO_ERROR;
+        }
+    }
+
+    return NAME_NOT_FOUND;
+}
+
+void BpBinder::onFrozenStateChanged(bool isFrozen) {
+    LOG_ALWAYS_FATAL_IF(isRpcBinder(), "onFrozenStateChanged is not supported for RPC Binder.");
+    LOG_ALWAYS_FATAL_IF(!kEnableKernelIpc, "Binder kernel driver disabled at build time");
+
+    ALOGV("Sending frozen state change notification for proxy %p handle %d, isFrozen=%s\n", this,
+          binderHandle(), isFrozen ? "true" : "false");
+
+    RpcMutexUniqueLock _l(mLock);
+    if (!mFrozen) {
+        return;
+    }
+    bool stateChanged = !mFrozen->initialStateReceived || mFrozen->isFrozen != isFrozen;
+    if (stateChanged) {
+        mFrozen->isFrozen = isFrozen;
+        mFrozen->initialStateReceived = true;
+        for (size_t i = 0; i < mFrozen->callbacks.size();) {
+            sp<FrozenStateChangeCallback> callback = mFrozen->callbacks.itemAt(i).promote();
+            if (callback != nullptr) {
+                callback->onStateChanged(wp<BpBinder>::fromExisting(this),
+                                         isFrozen ? FrozenStateChangeCallback::State::FROZEN
+                                                  : FrozenStateChangeCallback::State::UNFROZEN);
+                i++;
+            } else {
+                mFrozen->callbacks.removeItemsAt(i);
+            }
+        }
+    }
+}
+
 void BpBinder::reportOneDeath(const Obituary& obit)
 {
     sp<DeathRecipient> recipient = obit.recipient.promote();
@@ -569,19 +692,19 @@
 void* BpBinder::attachObject(const void* objectID, void* object, void* cleanupCookie,
                              object_cleanup_func func) {
     RpcMutexUniqueLock _l(mLock);
-    ALOGV("Attaching object %p to binder %p (manager=%p)", object, this, &mObjects);
-    return mObjects.attach(objectID, object, cleanupCookie, func);
+    ALOGV("Attaching object %p to binder %p (manager=%p)", object, this, &mObjectMgr);
+    return mObjectMgr.attach(objectID, object, cleanupCookie, func);
 }
 
 void* BpBinder::findObject(const void* objectID) const
 {
     RpcMutexUniqueLock _l(mLock);
-    return mObjects.find(objectID);
+    return mObjectMgr.find(objectID);
 }
 
 void* BpBinder::detachObject(const void* objectID) {
     RpcMutexUniqueLock _l(mLock);
-    return mObjects.detach(objectID);
+    return mObjectMgr.detach(objectID);
 }
 
 void BpBinder::withLock(const std::function<void()>& doWithLock) {
@@ -592,7 +715,7 @@
 sp<IBinder> BpBinder::lookupOrCreateWeak(const void* objectID, object_make_func make,
                                          const void* makeArgs) {
     RpcMutexUniqueLock _l(mLock);
-    return mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
+    return mObjectMgr.lookupOrCreateWeak(objectID, make, makeArgs);
 }
 
 BpBinder* BpBinder::remoteBinder()
@@ -686,6 +809,10 @@
         if (ipc) ipc->clearDeathNotification(binderHandle(), this);
         mObituaries = nullptr;
     }
+    if (mFrozen != nullptr) {
+        std::ignore = IPCThreadState::self()->removeFrozenStateChangeCallback(binderHandle(), this);
+        mFrozen.reset();
+    }
     mLock.unlock();
 
     if (obits != nullptr) {
diff --git a/libs/binder/Constants.h b/libs/binder/Constants.h
new file mode 100644
index 0000000..b75493c
--- /dev/null
+++ b/libs/binder/Constants.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android::binder {
+
+/**
+ * See also BINDER_VM_SIZE. In kernel binder, the sum of all transactions must be allocated in this
+ * space. Large transactions are very error prone. In general, we should work to reduce this limit.
+ * The same limit is used in RPC binder for consistency.
+ */
+constexpr size_t kLogTransactionsOverBytes = 300 * 1024;
+
+/**
+ * See b/392575419 - this limit is chosen for a specific usecase, because RPC binder does not have
+ * support for shared memory in the Android Baklava timeframe. This was 100 KB during and before
+ * Android V.
+ *
+ * Keeping this low helps preserve overall system performance. Transactions of this size are far too
+ * expensive to make multiple copies over binder or sockets, and they should be avoided if at all
+ * possible and transition to shared memory.
+ */
+constexpr size_t kRpcTransactionLimitBytes = 600 * 1024;
+
+} // namespace android::binder
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index 455a433..7263e23 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -82,7 +82,9 @@
 
     int ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
     if (ret < 0) {
-        return -errno;
+        int saved_errno = errno;
+        ALOGE("FdTrigger poll returned error: %d, with error: %s", ret, strerror(saved_errno));
+        return -saved_errno;
     }
     LOG_ALWAYS_FATAL_IF(ret == 0, "poll(%d) returns 0 with infinite timeout", transportFd.fd.get());
 
@@ -106,6 +108,7 @@
 
     // POLLNVAL: invalid FD number, e.g. not opened.
     if (pfd[0].revents & POLLNVAL) {
+        LOG_ALWAYS_FATAL("Invalid FD number (%d) in FdTrigger (POLLNVAL)", pfd[0].fd);
         return BAD_VALUE;
     }
 
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index 69b11c0..7b58046 100644
--- a/libs/binder/IBatteryStats.cpp
+++ b/libs/binder/IBatteryStats.cpp
@@ -66,14 +66,14 @@
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_START_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteStopAudio(int uid) {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
         data.writeInt32(uid);
-        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_STOP_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteResetVideo() {
@@ -85,7 +85,7 @@
     virtual void noteResetAudio() {
         Parcel data, reply;
         data.writeInterfaceToken(IBatteryStats::getInterfaceDescriptor());
-        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply);
+        remote()->transact(NOTE_RESET_AUDIO_TRANSACTION, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
     virtual void noteFlashlightOn(int uid) {
diff --git a/libs/binder/IMemory.cpp b/libs/binder/IMemory.cpp
index c6b0cb7..bb03e89 100644
--- a/libs/binder/IMemory.cpp
+++ b/libs/binder/IMemory.cpp
@@ -330,8 +330,8 @@
         if (err != NO_ERROR || // failed transaction
                 size != size64 || offset != offset64) { // ILP32 size check
             ALOGE("binder=%p transaction failed fd=%d, size=%zu, err=%d (%s)",
-                    IInterface::asBinder(this).get(),
-                    parcel_fd, size, err, strerror(-err));
+                  IInterface::asBinder(this).get(), parcel_fd, size, err,
+                  statusToString(err).c_str());
             return;
         }
 
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 984c93d..f7e0915 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -38,6 +38,10 @@
 #include "Utils.h"
 #include "binder_module.h"
 
+#if (defined(__ANDROID__) || defined(__Fuchsia__)) && !defined(BINDER_WITH_KERNEL_IPC)
+#error Android and Fuchsia are expected to have BINDER_WITH_KERNEL_IPC
+#endif
+
 #if LOG_NDEBUG
 
 #define IF_LOG_TRANSACTIONS() if (false)
@@ -89,26 +93,33 @@
         "BR_FROZEN_REPLY",
         "BR_ONEWAY_SPAM_SUSPECT",
         "BR_TRANSACTION_PENDING_FROZEN",
+        "BR_FROZEN_BINDER",
+        "BR_CLEAR_FREEZE_NOTIFICATION_DONE",
 };
 
-static const char *kCommandStrings[] = {
-    "BC_TRANSACTION",
-    "BC_REPLY",
-    "BC_ACQUIRE_RESULT",
-    "BC_FREE_BUFFER",
-    "BC_INCREFS",
-    "BC_ACQUIRE",
-    "BC_RELEASE",
-    "BC_DECREFS",
-    "BC_INCREFS_DONE",
-    "BC_ACQUIRE_DONE",
-    "BC_ATTEMPT_ACQUIRE",
-    "BC_REGISTER_LOOPER",
-    "BC_ENTER_LOOPER",
-    "BC_EXIT_LOOPER",
-    "BC_REQUEST_DEATH_NOTIFICATION",
-    "BC_CLEAR_DEATH_NOTIFICATION",
-    "BC_DEAD_BINDER_DONE"
+static const char* kCommandStrings[] = {
+        "BC_TRANSACTION",
+        "BC_REPLY",
+        "BC_ACQUIRE_RESULT",
+        "BC_FREE_BUFFER",
+        "BC_INCREFS",
+        "BC_ACQUIRE",
+        "BC_RELEASE",
+        "BC_DECREFS",
+        "BC_INCREFS_DONE",
+        "BC_ACQUIRE_DONE",
+        "BC_ATTEMPT_ACQUIRE",
+        "BC_REGISTER_LOOPER",
+        "BC_ENTER_LOOPER",
+        "BC_EXIT_LOOPER",
+        "BC_REQUEST_DEATH_NOTIFICATION",
+        "BC_CLEAR_DEATH_NOTIFICATION",
+        "BC_DEAD_BINDER_DONE",
+        "BC_TRANSACTION_SG",
+        "BC_REPLY_SG",
+        "BC_REQUEST_FREEZE_NOTIFICATION",
+        "BC_CLEAR_FREEZE_NOTIFICATION",
+        "BC_FREEZE_NOTIFICATION_DONE",
 };
 
 static const int64_t kWorkSourcePropagatedBitIndex = 32;
@@ -203,6 +214,18 @@
             out << ": death cookie " << (void*)(uint64_t)c;
         } break;
 
+        case BR_FROZEN_BINDER: {
+            const int32_t c = *cmd++;
+            const int32_t h = *cmd++;
+            const int32_t isFrozen = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c << " isFrozen: " << isFrozen;
+        } break;
+
+        case BR_CLEAR_FREEZE_NOTIFICATION_DONE: {
+            const int32_t c = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c;
+        } break;
+
         default:
             // no details to show for: BR_OK, BR_DEAD_REPLY,
             // BR_TRANSACTION_COMPLETE, BR_FINISHED
@@ -213,6 +236,15 @@
     return cmd;
 }
 
+static void printReturnCommandParcel(std::ostream& out, const Parcel& parcel) {
+    const void* cmds = parcel.data();
+    out << "\t" << HexDump(cmds, parcel.dataSize()) << "\n";
+    IF_LOG_COMMANDS() {
+        const void* end = parcel.data() + parcel.dataSize();
+        while (cmds < end) cmds = printReturnCommand(out, cmds);
+    }
+}
+
 static const void* printCommand(std::ostream& out, const void* _cmd) {
     static const size_t N = sizeof(kCommandStrings)/sizeof(kCommandStrings[0]);
     const int32_t* cmd = (const int32_t*)_cmd;
@@ -270,11 +302,23 @@
             out << ": handle=" << h << " (death cookie " << (void*)(uint64_t)c << ")";
         } break;
 
+        case BC_REQUEST_FREEZE_NOTIFICATION:
+        case BC_CLEAR_FREEZE_NOTIFICATION: {
+            const int32_t h = *cmd++;
+            const int32_t c = *cmd++;
+            out << ": handle=" << h << " (freeze cookie " << (void*)(uint64_t)c << ")";
+        } break;
+
         case BC_DEAD_BINDER_DONE: {
             const int32_t c = *cmd++;
             out << ": death cookie " << (void*)(uint64_t)c;
         } break;
 
+        case BC_FREEZE_NOTIFICATION_DONE: {
+            const int32_t c = *cmd++;
+            out << ": freeze cookie " << (void*)(uint64_t)c;
+        } break;
+
         default:
             // no details to show for: BC_REGISTER_LOOPER, BC_ENTER_LOOPER,
             // BC_EXIT_LOOPER
@@ -735,6 +779,7 @@
 {
     LOG_THREADPOOL("**** THREAD %p (PID %d) IS JOINING THE THREAD POOL\n", (void*)pthread_self(),
                    getpid());
+    mProcess->checkExpectingThreadPoolStart();
     mProcess->mCurrentThreads++;
     mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
 
@@ -953,6 +998,33 @@
     return NO_ERROR;
 }
 
+status_t IPCThreadState::addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy) {
+    static bool isSupported =
+            ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+    if (!isSupported) {
+        return INVALID_OPERATION;
+    }
+    proxy->getWeakRefs()->incWeak(proxy);
+    mOut.writeInt32(BC_REQUEST_FREEZE_NOTIFICATION);
+    mOut.writeInt32((int32_t)handle);
+    mOut.writePointer((uintptr_t)proxy);
+    flushCommands();
+    return NO_ERROR;
+}
+
+status_t IPCThreadState::removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy) {
+    static bool isSupported =
+            ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+    if (!isSupported) {
+        return INVALID_OPERATION;
+    }
+    mOut.writeInt32(BC_CLEAR_FREEZE_NOTIFICATION);
+    mOut.writeInt32((int32_t)handle);
+    mOut.writePointer((uintptr_t)proxy);
+    flushCommands();
+    return NO_ERROR;
+}
+
 IPCThreadState::IPCThreadState()
       : mProcess(ProcessState::self()),
         mServingStackPointer(nullptr),
@@ -1147,7 +1219,7 @@
             std::string message = logStream.str();
             ALOGI("%s", message.c_str());
         }
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
         if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
             err = NO_ERROR;
         else
@@ -1177,13 +1249,15 @@
 
     if (err >= NO_ERROR) {
         if (bwr.write_consumed > 0) {
-            if (bwr.write_consumed < mOut.dataSize())
+            if (bwr.write_consumed < mOut.dataSize()) {
+                std::ostringstream logStream;
+                printReturnCommandParcel(logStream, mIn);
                 LOG_ALWAYS_FATAL("Driver did not consume write buffer. "
-                                 "err: %s consumed: %zu of %zu",
-                                 statusToString(err).c_str(),
-                                 (size_t)bwr.write_consumed,
-                                 mOut.dataSize());
-            else {
+                                 "err: %s consumed: %zu of %zu.\n"
+                                 "Return command: %s",
+                                 statusToString(err).c_str(), (size_t)bwr.write_consumed,
+                                 mOut.dataSize(), logStream.str().c_str());
+            } else {
                 mOut.setDataSize(0);
                 processPostWriteDerefs();
             }
@@ -1194,14 +1268,8 @@
         }
         IF_LOG_COMMANDS() {
             std::ostringstream logStream;
-            logStream << "Remaining data size: " << mOut.dataSize() << "\n";
-            logStream << "Received commands from driver: ";
-            const void* cmds = mIn.data();
-            const void* end = mIn.data() + mIn.dataSize();
-            logStream << "\t" << HexDump(cmds, mIn.dataSize()) << "\n";
-            while (cmds < end) cmds = printReturnCommand(logStream, cmds);
-            std::string message = logStream.str();
-            ALOGI("%s", message.c_str());
+            printReturnCommandParcel(logStream, mIn);
+            ALOGI("%s", logStream.str().c_str());
         }
         return NO_ERROR;
     }
@@ -1487,6 +1555,26 @@
             proxy->getWeakRefs()->decWeak(proxy);
         } break;
 
+        case BR_FROZEN_BINDER: {
+            const struct binder_frozen_state_info* data =
+                    reinterpret_cast<const struct binder_frozen_state_info*>(
+                            mIn.readInplace(sizeof(struct binder_frozen_state_info)));
+            if (data == nullptr) {
+                result = UNKNOWN_ERROR;
+                break;
+            }
+            BpBinder* proxy = (BpBinder*)data->cookie;
+            bool isFrozen = mIn.readInt32() > 0;
+            proxy->getPrivateAccessor().onFrozenStateChanged(data->is_frozen);
+            mOut.writeInt32(BC_FREEZE_NOTIFICATION_DONE);
+            mOut.writePointer(data->cookie);
+        } break;
+
+        case BR_CLEAR_FREEZE_NOTIFICATION_DONE: {
+            BpBinder* proxy = (BpBinder*)mIn.readPointer();
+            proxy->getWeakRefs()->decWeak(proxy);
+        } break;
+
     case BR_FINISHED:
         result = TIMED_OUT;
         break;
@@ -1520,7 +1608,7 @@
         IPCThreadState* const self = static_cast<IPCThreadState*>(st);
         if (self) {
                 self->flushCommands();
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
         if (self->mProcess->mDriverFD >= 0) {
             ioctl(self->mProcess->mDriverFD, BINDER_THREAD_EXIT, 0);
         }
@@ -1536,7 +1624,7 @@
     binder_frozen_status_info info = {};
     info.pid = pid;
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_GET_FROZEN_INFO, &info) < 0)
         ret = -errno;
 #endif
@@ -1555,7 +1643,7 @@
     info.timeout_ms = timeout_ms;
 
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0)
         ret = -errno;
 #endif
@@ -1573,7 +1661,7 @@
     if (!ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::EXTENDED_ERROR))
         return;
 
-#if defined(__ANDROID__)
+#if defined(BINDER_WITH_KERNEL_IPC)
     if (ioctl(self()->mProcess->mDriverFD, BINDER_GET_EXTENDED_ERROR, &ee) < 0) {
         ALOGE("Failed to get extended error: %s", strerror(errno));
         return;
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 8b80aed..c9ca646 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -14,9 +14,11 @@
  * limitations under the License.
  */
 
+#include <sys/socket.h>
 #define LOG_TAG "ServiceManagerCppClient"
 
 #include <binder/IServiceManager.h>
+#include <binder/IServiceManagerUnitTestHelper.h>
 #include "BackendUnifiedServiceManager.h"
 
 #include <inttypes.h>
@@ -24,19 +26,28 @@
 #include <chrono>
 #include <condition_variable>
 
+#include <FdTrigger.h>
+#include <RpcSocketAddress.h>
 #include <android-base/properties.h>
+#include <android/os/BnAccessor.h>
 #include <android/os/BnServiceCallback.h>
+#include <android/os/BnServiceManager.h>
 #include <android/os/IAccessor.h>
 #include <android/os/IServiceManager.h>
 #include <binder/IPCThreadState.h>
 #include <binder/Parcel.h>
+#include <binder/RpcSession.h>
 #include <utils/String8.h>
-
+#include <variant>
 #ifndef __ANDROID_VNDK__
 #include <binder/IPermissionController.h>
 #endif
 
-#ifdef __ANDROID__
+#if !(defined(__ANDROID__) || defined(__FUCHSIA))
+#define BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT
+#endif
+
+#if !defined(BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT)
 #include <cutils/properties.h>
 #else
 #include "ServiceManagerHost.h"
@@ -79,10 +90,9 @@
 IServiceManager::~IServiceManager() {}
 
 // From the old libbinder IServiceManager interface to IServiceManager.
-class ServiceManagerShim : public IServiceManager
-{
+class CppBackendShim : public IServiceManager {
 public:
-    explicit ServiceManagerShim (const sp<AidlServiceManager>& impl);
+    explicit CppBackendShim(const sp<BackendUnifiedServiceManager>& impl);
 
     sp<IBinder> getService(const String16& name) const override;
     sp<IBinder> checkService(const String16& name) const override;
@@ -121,6 +131,8 @@
     }
     IBinder* onAsBinder() override { return IInterface::asBinder(mUnifiedServiceManager).get(); }
 
+    void enableAddServiceCache(bool value) { mUnifiedServiceManager->enableAddServiceCache(value); }
+
 protected:
     sp<BackendUnifiedServiceManager> mUnifiedServiceManager;
     // AidlRegistrationCallback -> services that its been registered for
@@ -136,26 +148,189 @@
                                           sp<RegistrationWaiter>* waiter);
 
     // Directly get the service in a way that, for lazy services, requests the service to be started
-    // if it is not currently started. This way, calls directly to ServiceManagerShim::getService
+    // if it is not currently started. This way, calls directly to CppBackendShim::getService
     // will still have the 5s delay that is expected by a large amount of Android code.
     //
-    // When implementing ServiceManagerShim, use realGetService instead of
-    // mUnifiedServiceManager->getService so that it can be overridden in ServiceManagerHostShim.
+    // When implementing CppBackendShim, use realGetService instead of
+    // mUnifiedServiceManager->getService so that it can be overridden in CppServiceManagerHostShim.
     virtual Status realGetService(const std::string& name, sp<IBinder>* _aidl_return) {
         Service service;
         Status status = mUnifiedServiceManager->getService2(name, &service);
-        *_aidl_return = service.get<Service::Tag::binder>();
+        auto serviceWithMetadata = service.get<Service::Tag::serviceWithMetadata>();
+        *_aidl_return = serviceWithMetadata.service;
         return status;
     }
 };
 
+class AccessorProvider {
+public:
+    AccessorProvider(std::set<std::string>&& instances, RpcAccessorProvider&& provider)
+          : mInstances(std::move(instances)), mProvider(std::move(provider)) {}
+    sp<IBinder> provide(const String16& name) {
+        if (mInstances.count(String8(name).c_str()) > 0) {
+            return mProvider(name);
+        } else {
+            return nullptr;
+        }
+    }
+    const std::set<std::string>& instances() { return mInstances; }
+
+private:
+    AccessorProvider() = delete;
+
+    std::set<std::string> mInstances;
+    RpcAccessorProvider mProvider;
+};
+
+class AccessorProviderEntry {
+public:
+    AccessorProviderEntry(std::shared_ptr<AccessorProvider>&& provider)
+          : mProvider(std::move(provider)) {}
+    std::shared_ptr<AccessorProvider> mProvider;
+
+private:
+    AccessorProviderEntry() = delete;
+};
+
 [[clang::no_destroy]] static std::once_flag gSmOnce;
 [[clang::no_destroy]] static sp<IServiceManager> gDefaultServiceManager;
+[[clang::no_destroy]] static std::mutex gAccessorProvidersMutex;
+[[clang::no_destroy]] static std::vector<AccessorProviderEntry> gAccessorProviders;
+
+class LocalAccessor : public android::os::BnAccessor {
+public:
+    LocalAccessor(const String16& instance, RpcSocketAddressProvider&& connectionInfoProvider)
+          : mInstance(instance), mConnectionInfoProvider(std::move(connectionInfoProvider)) {
+        LOG_ALWAYS_FATAL_IF(!mConnectionInfoProvider,
+                            "LocalAccessor object needs a valid connection info provider");
+    }
+
+    ~LocalAccessor() {
+        if (mOnDelete) mOnDelete();
+    }
+
+    ::android::binder::Status addConnection(::android::os::ParcelFileDescriptor* outFd) {
+        using android::os::IAccessor;
+        sockaddr_storage addrStorage;
+        std::unique_ptr<FdTrigger> trigger = FdTrigger::make();
+        RpcTransportFd fd;
+        status_t status =
+                mConnectionInfoProvider(mInstance, reinterpret_cast<sockaddr*>(&addrStorage),
+                                        sizeof(addrStorage));
+        if (status != OK) {
+            const std::string error = "The connection info provider was unable to provide "
+                                      "connection info for instance " +
+                    std::string(String8(mInstance).c_str()) +
+                    " with status: " + statusToString(status);
+            ALOGE("%s", error.c_str());
+            return Status::fromServiceSpecificError(IAccessor::ERROR_CONNECTION_INFO_NOT_FOUND,
+                                                    error.c_str());
+        }
+        if (addrStorage.ss_family == AF_VSOCK) {
+            sockaddr_vm* addr = reinterpret_cast<sockaddr_vm*>(&addrStorage);
+            status = singleSocketConnection(VsockSocketAddress(addr->svm_cid, addr->svm_port),
+                                            trigger, &fd);
+        } else if (addrStorage.ss_family == AF_UNIX) {
+            sockaddr_un* addr = reinterpret_cast<sockaddr_un*>(&addrStorage);
+            status = singleSocketConnection(UnixSocketAddress(addr->sun_path), trigger, &fd);
+        } else if (addrStorage.ss_family == AF_INET) {
+            sockaddr_in* addr = reinterpret_cast<sockaddr_in*>(&addrStorage);
+            status = singleSocketConnection(InetSocketAddress(reinterpret_cast<sockaddr*>(addr),
+                                                              sizeof(sockaddr_in),
+                                                              inet_ntoa(addr->sin_addr),
+                                                              ntohs(addr->sin_port)),
+                                            trigger, &fd);
+        } else {
+            const std::string error =
+                    "Unsupported socket family type or the ConnectionInfoProvider failed to find a "
+                    "valid address. Family type: " +
+                    std::to_string(addrStorage.ss_family);
+            ALOGE("%s", error.c_str());
+            return Status::fromServiceSpecificError(IAccessor::ERROR_UNSUPPORTED_SOCKET_FAMILY,
+                                                    error.c_str());
+        }
+        if (status != OK) {
+            const std::string error = "Failed to connect to socket for " +
+                    std::string(String8(mInstance).c_str()) +
+                    " with status: " + statusToString(status);
+            ALOGE("%s", error.c_str());
+            int err = 0;
+            if (status == -EACCES) {
+                err = IAccessor::ERROR_FAILED_TO_CONNECT_EACCES;
+            } else {
+                err = IAccessor::ERROR_FAILED_TO_CONNECT_TO_SOCKET;
+            }
+            return Status::fromServiceSpecificError(err, error.c_str());
+        }
+        *outFd = os::ParcelFileDescriptor(std::move(fd.fd));
+        return Status::ok();
+    }
+
+    ::android::binder::Status getInstanceName(String16* instance) {
+        *instance = mInstance;
+        return Status::ok();
+    }
+
+private:
+    LocalAccessor() = delete;
+    String16 mInstance;
+    RpcSocketAddressProvider mConnectionInfoProvider;
+    std::function<void()> mOnDelete;
+};
+
+android::binder::Status getInjectedAccessor(const std::string& name,
+                                            android::os::Service* service) {
+    std::vector<AccessorProviderEntry> copiedProviders;
+    {
+        std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+        copiedProviders.insert(copiedProviders.begin(), gAccessorProviders.begin(),
+                               gAccessorProviders.end());
+    }
+
+    // Unlocked to call the providers. This requires the providers to be
+    // threadsafe and not contain any references to objects that could be
+    // deleted.
+    for (const auto& provider : copiedProviders) {
+        sp<IBinder> binder = provider.mProvider->provide(String16(name.c_str()));
+        if (binder == nullptr) continue;
+        status_t status = validateAccessor(String16(name.c_str()), binder);
+        if (status != OK) {
+            ALOGE("A provider returned a binder that is not an IAccessor for instance %s. Status: "
+                  "%s",
+                  name.c_str(), statusToString(status).c_str());
+            return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
+        }
+        *service = os::Service::make<os::Service::Tag::accessor>(binder);
+        return android::binder::Status::ok();
+    }
+
+    *service = os::Service::make<os::Service::Tag::accessor>(nullptr);
+    return android::binder::Status::ok();
+}
+
+void appendInjectedAccessorServices(std::vector<std::string>* list) {
+    LOG_ALWAYS_FATAL_IF(list == nullptr,
+                        "Attempted to get list of services from Accessors with nullptr");
+    std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+    for (const auto& entry : gAccessorProviders) {
+        list->insert(list->end(), entry.mProvider->instances().begin(),
+                     entry.mProvider->instances().end());
+    }
+}
+
+void forEachInjectedAccessorService(const std::function<void(const std::string&)>& f) {
+    std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+    for (const auto& entry : gAccessorProviders) {
+        for (const auto& instance : entry.mProvider->instances()) {
+            f(instance);
+        }
+    }
+}
 
 sp<IServiceManager> defaultServiceManager()
 {
     std::call_once(gSmOnce, []() {
-        gDefaultServiceManager = sp<ServiceManagerShim>::make(getBackendUnifiedServiceManager());
+        gDefaultServiceManager = sp<CppBackendShim>::make(getBackendUnifiedServiceManager());
     });
 
     return gDefaultServiceManager;
@@ -173,6 +348,126 @@
     }
 }
 
+sp<IServiceManager> getServiceManagerShimFromAidlServiceManagerForTests(
+        const sp<AidlServiceManager>& sm) {
+    return sp<CppBackendShim>::make(sp<BackendUnifiedServiceManager>::make(sm));
+}
+
+// gAccessorProvidersMutex must be locked already
+static bool isInstanceProvidedLocked(const std::string& instance) {
+    return gAccessorProviders.end() !=
+            std::find_if(gAccessorProviders.begin(), gAccessorProviders.end(),
+                         [&instance](const AccessorProviderEntry& entry) {
+                             return entry.mProvider->instances().count(instance) > 0;
+                         });
+}
+
+std::weak_ptr<AccessorProvider> addAccessorProvider(std::set<std::string>&& instances,
+                                                    RpcAccessorProvider&& providerCallback) {
+    if (instances.empty()) {
+        ALOGE("Set of instances is empty! Need a non empty set of instances to provide for.");
+        return std::weak_ptr<AccessorProvider>();
+    }
+    std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+    for (const auto& instance : instances) {
+        if (isInstanceProvidedLocked(instance)) {
+            ALOGE("The instance %s is already provided for by a previously added "
+                  "RpcAccessorProvider.",
+                  instance.c_str());
+            return std::weak_ptr<AccessorProvider>();
+        }
+    }
+    std::shared_ptr<AccessorProvider> provider =
+            std::make_shared<AccessorProvider>(std::move(instances), std::move(providerCallback));
+    std::weak_ptr<AccessorProvider> receipt = provider;
+    gAccessorProviders.push_back(AccessorProviderEntry(std::move(provider)));
+
+    return receipt;
+}
+
+status_t removeAccessorProvider(std::weak_ptr<AccessorProvider> wProvider) {
+    std::shared_ptr<AccessorProvider> provider = wProvider.lock();
+    if (provider == nullptr) {
+        ALOGE("The provider supplied to removeAccessorProvider has already been removed or the "
+              "argument to this function was nullptr.");
+        return BAD_VALUE;
+    }
+    std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+    size_t sizeBefore = gAccessorProviders.size();
+    gAccessorProviders.erase(std::remove_if(gAccessorProviders.begin(), gAccessorProviders.end(),
+                                            [&](AccessorProviderEntry entry) {
+                                                return entry.mProvider == provider;
+                                            }),
+                             gAccessorProviders.end());
+    if (sizeBefore == gAccessorProviders.size()) {
+        ALOGE("Failed to find an AccessorProvider for removeAccessorProvider");
+        return NAME_NOT_FOUND;
+    }
+
+    return OK;
+}
+
+status_t validateAccessor(const String16& instance, const sp<IBinder>& binder) {
+    if (binder == nullptr) {
+        ALOGE("Binder is null");
+        return BAD_VALUE;
+    }
+    sp<IAccessor> accessor = checked_interface_cast<IAccessor>(binder);
+    if (accessor == nullptr) {
+        ALOGE("This binder for %s is not an IAccessor binder", String8(instance).c_str());
+        return BAD_TYPE;
+    }
+    String16 reportedInstance;
+    Status status = accessor->getInstanceName(&reportedInstance);
+    if (!status.isOk()) {
+        ALOGE("Failed to validate the binder being used to create a new ARpc_Accessor for %s with "
+              "status: %s",
+              String8(instance).c_str(), status.toString8().c_str());
+        return NAME_NOT_FOUND;
+    }
+    if (reportedInstance != instance) {
+        ALOGE("Instance %s doesn't match the Accessor's instance of %s", String8(instance).c_str(),
+              String8(reportedInstance).c_str());
+        return NAME_NOT_FOUND;
+    }
+    return OK;
+}
+
+sp<IBinder> createAccessor(const String16& instance,
+                           RpcSocketAddressProvider&& connectionInfoProvider) {
+    // Try to create a new accessor
+    if (!connectionInfoProvider) {
+        ALOGE("Could not find an Accessor for %s and no ConnectionInfoProvider provided to "
+              "create a new one",
+              String8(instance).c_str());
+        return nullptr;
+    }
+    sp<IBinder> binder = sp<LocalAccessor>::make(instance, std::move(connectionInfoProvider));
+    return binder;
+}
+
+status_t delegateAccessor(const String16& name, const sp<IBinder>& accessor,
+                          sp<IBinder>* delegator) {
+    LOG_ALWAYS_FATAL_IF(delegator == nullptr, "delegateAccessor called with a null out param");
+    if (accessor == nullptr) {
+        ALOGW("Accessor argument to delegateAccessor is null.");
+        *delegator = nullptr;
+        return OK;
+    }
+    status_t status = validateAccessor(name, accessor);
+    if (status != OK) {
+        ALOGE("The provided accessor binder is not an IAccessor for instance %s. Status: "
+              "%s",
+              String8(name).c_str(), statusToString(status).c_str());
+        return status;
+    }
+    // validateAccessor already called checked_interface_cast and made sure this
+    // is a valid accessor object.
+    *delegator = sp<android::os::IAccessorDelegator>::make(interface_cast<IAccessor>(accessor));
+
+    return OK;
+}
+
 #if !defined(__ANDROID_VNDK__)
 // IPermissionController is not accessible to vendors
 
@@ -253,8 +548,11 @@
     }
 }
 
+#endif //__ANDROID_VNDK__
+
 void* openDeclaredPassthroughHal(const String16& interface, const String16& instance, int flag) {
-#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__) && !defined(__ANDROID_NATIVE_BRIDGE__)
+#if defined(__ANDROID__) && !defined(__ANDROID_VENDOR__) && !defined(__ANDROID_RECOVERY__) && \
+        !defined(__ANDROID_NATIVE_BRIDGE__)
     sp<IServiceManager> sm = defaultServiceManager();
     String16 name = interface + String16("/") + instance;
     if (!sm->isDeclared(name)) {
@@ -274,27 +572,24 @@
 #endif
 }
 
-#endif //__ANDROID_VNDK__
-
 // ----------------------------------------------------------------------
 
-ServiceManagerShim::ServiceManagerShim(const sp<AidlServiceManager>& impl) {
-    mUnifiedServiceManager = sp<BackendUnifiedServiceManager>::make(impl);
-}
+CppBackendShim::CppBackendShim(const sp<BackendUnifiedServiceManager>& impl)
+      : mUnifiedServiceManager(impl) {}
 
 // This implementation could be simplified and made more efficient by delegating
 // to waitForService. However, this changes the threading structure in some
 // cases and could potentially break prebuilts. Once we have higher logistical
 // complexity, this could be attempted.
-sp<IBinder> ServiceManagerShim::getService(const String16& name) const
-{
+sp<IBinder> CppBackendShim::getService(const String16& name) const {
     static bool gSystemBootCompleted = false;
 
     sp<IBinder> svc = checkService(name);
     if (svc != nullptr) return svc;
 
+    sp<ProcessState> self = ProcessState::selfOrNull();
     const bool isVendorService =
-        strcmp(ProcessState::self()->getDriverName().c_str(), "/dev/vndbinder") == 0;
+            self && strcmp(self->getDriverName().c_str(), "/dev/vndbinder") == 0;
     constexpr auto timeout = 5s;
     const auto startTime = std::chrono::steady_clock::now();
     // Vendor code can't access system properties
@@ -311,7 +606,7 @@
     const useconds_t sleepTime = gSystemBootCompleted ? 1000 : 100;
 
     ALOGI("Waiting for service '%s' on '%s'...", String8(name).c_str(),
-          ProcessState::self()->getDriverName().c_str());
+          self ? self->getDriverName().c_str() : "RPC accessors only");
 
     int n = 0;
     while (std::chrono::steady_clock::now() - startTime < timeout) {
@@ -331,25 +626,22 @@
     return nullptr;
 }
 
-sp<IBinder> ServiceManagerShim::checkService(const String16& name) const
-{
+sp<IBinder> CppBackendShim::checkService(const String16& name) const {
     Service ret;
-    if (!mUnifiedServiceManager->checkService(String8(name).c_str(), &ret).isOk()) {
+    if (!mUnifiedServiceManager->checkService2(String8(name).c_str(), &ret).isOk()) {
         return nullptr;
     }
-    return ret.get<Service::Tag::binder>();
+    return ret.get<Service::Tag::serviceWithMetadata>().service;
 }
 
-status_t ServiceManagerShim::addService(const String16& name, const sp<IBinder>& service,
-                                        bool allowIsolated, int dumpsysPriority)
-{
+status_t CppBackendShim::addService(const String16& name, const sp<IBinder>& service,
+                                    bool allowIsolated, int dumpsysPriority) {
     Status status = mUnifiedServiceManager->addService(String8(name).c_str(), service,
                                                        allowIsolated, dumpsysPriority);
     return status.exceptionCode();
 }
 
-Vector<String16> ServiceManagerShim::listServices(int dumpsysPriority)
-{
+Vector<String16> CppBackendShim::listServices(int dumpsysPriority) {
     std::vector<std::string> ret;
     if (!mUnifiedServiceManager->listServices(dumpsysPriority, &ret).isOk()) {
         return {};
@@ -363,8 +655,7 @@
     return res;
 }
 
-sp<IBinder> ServiceManagerShim::waitForService(const String16& name16)
-{
+sp<IBinder> CppBackendShim::waitForService(const String16& name16) {
     class Waiter : public android::os::BnServiceCallback {
         Status onRegistration(const std::string& /*name*/,
                               const sp<IBinder>& binder) override {
@@ -397,7 +688,8 @@
     if (Status status = realGetService(name, &out); !status.isOk()) {
         ALOGW("Failed to getService in waitForService for %s: %s", name.c_str(),
               status.toString8().c_str());
-        if (0 == ProcessState::self()->getThreadPoolMaxTotalThreadCount()) {
+        sp<ProcessState> self = ProcessState::selfOrNull();
+        if (self && 0 == self->getThreadPoolMaxTotalThreadCount()) {
             ALOGW("Got service, but may be racey because we could not wait efficiently for it. "
                   "Threadpool has 0 guaranteed threads. "
                   "Is the threadpool configured properly? "
@@ -431,9 +723,10 @@
             if (waiter->mBinder != nullptr) return waiter->mBinder;
         }
 
+        sp<ProcessState> self = ProcessState::selfOrNull();
         ALOGW("Waited one second for %s (is service started? Number of threads started in the "
               "threadpool: %zu. Are binder threads started and available?)",
-              name.c_str(), ProcessState::self()->getThreadPoolMaxTotalThreadCount());
+              name.c_str(), self ? self->getThreadPoolMaxTotalThreadCount() : 0);
 
         // Handle race condition for lazy services. Here is what can happen:
         // - the service dies (not processed by init yet).
@@ -453,7 +746,7 @@
     }
 }
 
-bool ServiceManagerShim::isDeclared(const String16& name) {
+bool CppBackendShim::isDeclared(const String16& name) {
     bool declared;
     if (Status status = mUnifiedServiceManager->isDeclared(String8(name).c_str(), &declared);
         !status.isOk()) {
@@ -464,7 +757,7 @@
     return declared;
 }
 
-Vector<String16> ServiceManagerShim::getDeclaredInstances(const String16& interface) {
+Vector<String16> CppBackendShim::getDeclaredInstances(const String16& interface) {
     std::vector<std::string> out;
     if (Status status =
                 mUnifiedServiceManager->getDeclaredInstances(String8(interface).c_str(), &out);
@@ -482,7 +775,7 @@
     return res;
 }
 
-std::optional<String16> ServiceManagerShim::updatableViaApex(const String16& name) {
+std::optional<String16> CppBackendShim::updatableViaApex(const String16& name) {
     std::optional<std::string> declared;
     if (Status status = mUnifiedServiceManager->updatableViaApex(String8(name).c_str(), &declared);
         !status.isOk()) {
@@ -493,7 +786,7 @@
     return declared ? std::optional<String16>(String16(declared.value().c_str())) : std::nullopt;
 }
 
-Vector<String16> ServiceManagerShim::getUpdatableNames(const String16& apexName) {
+Vector<String16> CppBackendShim::getUpdatableNames(const String16& apexName) {
     std::vector<std::string> out;
     if (Status status = mUnifiedServiceManager->getUpdatableNames(String8(apexName).c_str(), &out);
         !status.isOk()) {
@@ -510,7 +803,7 @@
     return res;
 }
 
-std::optional<IServiceManager::ConnectionInfo> ServiceManagerShim::getConnectionInfo(
+std::optional<IServiceManager::ConnectionInfo> CppBackendShim::getConnectionInfo(
         const String16& name) {
     std::optional<os::ConnectionInfo> connectionInfo;
     if (Status status =
@@ -525,8 +818,8 @@
             : std::nullopt;
 }
 
-status_t ServiceManagerShim::registerForNotifications(const String16& name,
-                                                      const sp<AidlRegistrationCallback>& cb) {
+status_t CppBackendShim::registerForNotifications(const String16& name,
+                                                  const sp<AidlRegistrationCallback>& cb) {
     if (cb == nullptr) {
         ALOGE("%s: null cb passed", __FUNCTION__);
         return BAD_VALUE;
@@ -545,9 +838,9 @@
     return OK;
 }
 
-void ServiceManagerShim::removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
-                                                          ServiceCallbackMap::iterator* it,
-                                                          sp<RegistrationWaiter>* waiter) {
+void CppBackendShim::removeRegistrationCallbackLocked(const sp<AidlRegistrationCallback>& cb,
+                                                      ServiceCallbackMap::iterator* it,
+                                                      sp<RegistrationWaiter>* waiter) {
     std::vector<LocalRegistrationAndWaiter>& localRegistrationAndWaiters = (*it)->second;
     for (auto lit = localRegistrationAndWaiters.begin();
          lit != localRegistrationAndWaiters.end();) {
@@ -566,8 +859,8 @@
     }
 }
 
-status_t ServiceManagerShim::unregisterForNotifications(const String16& name,
-                                                        const sp<AidlRegistrationCallback>& cb) {
+status_t CppBackendShim::unregisterForNotifications(const String16& name,
+                                                    const sp<AidlRegistrationCallback>& cb) {
     if (cb == nullptr) {
         ALOGE("%s: null cb passed", __FUNCTION__);
         return BAD_VALUE;
@@ -596,7 +889,7 @@
     return OK;
 }
 
-std::vector<IServiceManager::ServiceDebugInfo> ServiceManagerShim::getServiceDebugInfo() {
+std::vector<IServiceManager::ServiceDebugInfo> CppBackendShim::getServiceDebugInfo() {
     std::vector<os::ServiceDebugInfo> serviceDebugInfos;
     std::vector<IServiceManager::ServiceDebugInfo> ret;
     if (Status status = mUnifiedServiceManager->getServiceDebugInfo(&serviceDebugInfos);
@@ -613,22 +906,22 @@
     return ret;
 }
 
-#ifndef __ANDROID__
-// ServiceManagerShim for host. Implements the old libbinder android::IServiceManager API.
+#if defined(BINDER_SERVICEMANAGEMENT_DELEGATION_SUPPORT)
+// CppBackendShim for host. Implements the old libbinder android::IServiceManager API.
 // The internal implementation of the AIDL interface android::os::IServiceManager calls into
 // on-device service manager.
-class ServiceManagerHostShim : public ServiceManagerShim {
+class CppServiceManagerHostShim : public CppBackendShim {
 public:
-    ServiceManagerHostShim(const sp<AidlServiceManager>& impl,
-                           const RpcDelegateServiceManagerOptions& options)
-          : ServiceManagerShim(impl), mOptions(options) {}
-    // ServiceManagerShim::getService is based on checkService, so no need to override it.
+    CppServiceManagerHostShim(const sp<AidlServiceManager>& impl,
+                              const RpcDelegateServiceManagerOptions& options)
+          : CppBackendShim(sp<BackendUnifiedServiceManager>::make(impl)), mOptions(options) {}
+    // CppBackendShim::getService is based on checkService, so no need to override it.
     sp<IBinder> checkService(const String16& name) const override {
         return getDeviceService({String8(name).c_str()}, mOptions);
     }
 
 protected:
-    // Override realGetService for ServiceManagerShim::waitForService.
+    // Override realGetService for CppBackendShim::waitForService.
     Status realGetService(const std::string& name, sp<IBinder>* _aidl_return) override {
         *_aidl_return = getDeviceService({"-g", name}, mOptions);
         return Status::ok();
@@ -649,7 +942,7 @@
         ALOGE("getDeviceService(\"manager\") returns non service manager");
         return nullptr;
     }
-    return sp<ServiceManagerHostShim>::make(interface, options);
+    return sp<CppServiceManagerHostShim>::make(interface, options);
 }
 #endif
 
diff --git a/libs/binder/LazyServiceRegistrar.cpp b/libs/binder/LazyServiceRegistrar.cpp
index 0f0af0b..27cfe0c 100644
--- a/libs/binder/LazyServiceRegistrar.cpp
+++ b/libs/binder/LazyServiceRegistrar.cpp
@@ -19,7 +19,6 @@
 #include <android/os/BnClientCallback.h>
 #include <android/os/IServiceManager.h>
 #include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
 #include <binder/LazyServiceRegistrar.h>
 
 namespace android {
@@ -134,6 +133,12 @@
     std::string regStr = (reRegister) ? "Re-registering" : "Registering";
     ALOGI("%s service %s", regStr.c_str(), name.c_str());
 
+    if (dumpFlags & android::os::IServiceManager::FLAG_IS_LAZY_SERVICE) {
+        ALOGW("FLAG_IS_LAZY_SERVICE flag already set. This should only be set by "
+              "ClientCounterCallbackImpl in LazyServiceRegistrar");
+    }
+    dumpFlags |= android::os::IServiceManager::FLAG_IS_LAZY_SERVICE;
+
     if (Status status = manager->addService(name.c_str(), service, allowIsolated, dumpFlags);
         !status.isOk()) {
         ALOGE("Failed to register service %s (%s)", name.c_str(), status.toString8().c_str());
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 04869a1..64b1fd4 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -27,6 +27,7 @@
 LIBBINDER_EXPORTED void trace_begin(uint64_t tag, const char* name);
 LIBBINDER_EXPORTED void trace_end(uint64_t tag);
 LIBBINDER_EXPORTED void trace_int(uint64_t tag, const char* name, int32_t value);
+LIBBINDER_EXPORTED uint64_t get_trace_enabled_tags();
 
 status_t setNonBlocking(borrowed_fd fd);
 
diff --git a/libs/binder/OS_android.cpp b/libs/binder/OS_android.cpp
index 893ee15..4e9230c 100644
--- a/libs/binder/OS_android.cpp
+++ b/libs/binder/OS_android.cpp
@@ -48,6 +48,10 @@
     atrace_int(tag, name, value);
 }
 
+uint64_t get_trace_enabled_tags() {
+    return atrace_enabled_tags;
+}
+
 } // namespace os
 
 // Legacy trace symbol. To be removed once all of downstream rebuilds.
diff --git a/libs/binder/OS_non_android_linux.cpp b/libs/binder/OS_non_android_linux.cpp
index 0c64eb6..6bba823 100644
--- a/libs/binder/OS_non_android_linux.cpp
+++ b/libs/binder/OS_non_android_linux.cpp
@@ -41,6 +41,10 @@
 
 void trace_int(uint64_t, const char*, int32_t) {}
 
+uint64_t get_trace_enabled_tags() {
+    return 0;
+}
+
 uint64_t GetThreadId() {
     return syscall(__NR_gettid);
 }
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index f30b2aa..1e83c35 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -299,8 +299,13 @@
             obj.handle = handle;
             obj.cookie = 0;
         } else {
+#if __linux__
             int policy = local->getMinSchedulerPolicy();
             int priority = local->getMinSchedulerPriority();
+#else
+            int policy = 0;
+            int priority = 0;
+#endif
 
             if (policy != 0 || priority != 0) {
                 // override value, since it is set explicitly
@@ -616,6 +621,7 @@
         }
 #else
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
         return INVALID_OPERATION;
 #endif // BINDER_WITH_KERNEL_IPC
     } else {
@@ -668,7 +674,8 @@
                 // FD was unowned in the source parcel.
                 int newFd = -1;
                 if (status_t status = binder::os::dupFileDescriptor(oldFd, &newFd); status != OK) {
-                    ALOGW("Failed to duplicate file descriptor %d: %s", oldFd, strerror(-status));
+                    ALOGW("Failed to duplicate file descriptor %d: %s", oldFd,
+                          statusToString(status).c_str());
                 }
                 rpcFields->mFds->emplace_back(unique_fd(newFd));
                 // Fixup the index in the data.
@@ -683,7 +690,7 @@
     return err;
 }
 
-int Parcel::compareData(const Parcel& other) {
+int Parcel::compareData(const Parcel& other) const {
     size_t size = dataSize();
     if (size != other.dataSize()) {
         return size < other.dataSize() ? -1 : 1;
@@ -796,6 +803,7 @@
         setDataPosition(initPosition);
 #else
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
 #endif
     } else if (const auto* rpcFields = maybeRpcFields(); rpcFields && rpcFields->mFds) {
         for (const auto& fd : *rpcFields->mFds) {
@@ -838,9 +846,10 @@
         }
 #else
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
         return INVALID_OPERATION;
 #endif // BINDER_WITH_KERNEL_IPC
-    } else if (const auto* rpcFields = maybeRpcFields()) {
+    } else if (maybeRpcFields()) {
         return INVALID_OPERATION;
     }
     return NO_ERROR;
@@ -878,6 +887,7 @@
         }
 #else
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
         return INVALID_OPERATION;
 #endif // BINDER_WITH_KERNEL_IPC
     } else if (const auto* rpcFields = maybeRpcFields()) {
@@ -970,6 +980,7 @@
         writeInt32(kHeader);
 #else  // BINDER_WITH_KERNEL_IPC
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
         return INVALID_OPERATION;
 #endif // BINDER_WITH_KERNEL_IPC
     }
@@ -1060,6 +1071,7 @@
 #else  // BINDER_WITH_KERNEL_IPC
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
         (void)threadState;
+        (void)kernelFields;
         return false;
 #endif // BINDER_WITH_KERNEL_IPC
     }
@@ -1313,10 +1325,6 @@
 status_t Parcel::writeUniqueFileDescriptorVector(const std::optional<std::vector<unique_fd>>& val) {
     return writeData(val);
 }
-status_t Parcel::writeUniqueFileDescriptorVector(
-        const std::unique_ptr<std::vector<unique_fd>>& val) {
-    return writeData(val);
-}
 
 status_t Parcel::writeStrongBinderVector(const std::vector<sp<IBinder>>& val) { return writeData(val); }
 status_t Parcel::writeStrongBinderVector(const std::optional<std::vector<sp<IBinder>>>& val) { return writeData(val); }
@@ -1372,10 +1380,6 @@
 status_t Parcel::readUniqueFileDescriptorVector(std::optional<std::vector<unique_fd>>* val) const {
     return readData(val);
 }
-status_t Parcel::readUniqueFileDescriptorVector(
-        std::unique_ptr<std::vector<unique_fd>>* val) const {
-    return readData(val);
-}
 status_t Parcel::readUniqueFileDescriptorVector(std::vector<unique_fd>* val) const {
     return readData(val);
 }
@@ -1725,7 +1729,9 @@
                 }
             }
         }
-        ::munmap(ptr, len);
+        if (::munmap(ptr, len) == -1) {
+            ALOGW("munmap() failed: %s", strerror(errno));
+        }
     }
     ::close(fd);
     return status;
@@ -2695,6 +2701,7 @@
         }
 #else  // BINDER_WITH_KERNEL_IPC
         LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+        (void)kernelFields;
 #endif // BINDER_WITH_KERNEL_IPC
     } else if (auto* rpcFields = maybeRpcFields()) {
         rpcFields->mFds.reset();
@@ -2982,14 +2989,15 @@
         return continueWrite(desired);
     }
 
+    releaseObjects();
+
     uint8_t* data = reallocZeroFree(mData, mDataCapacity, desired, mDeallocZero);
     if (!data && desired > mDataCapacity) {
+        LOG_ALWAYS_FATAL("out of memory");
         mError = NO_MEMORY;
         return NO_MEMORY;
     }
 
-    releaseObjects();
-
     if (data || desired == 0) {
         LOG_ALLOC("Parcel %p: restart from %zu to %zu capacity", this, mDataCapacity, desired);
         if (mDataCapacity > desired) {
@@ -3283,14 +3291,6 @@
 }
 
 #ifdef BINDER_WITH_KERNEL_IPC
-size_t Parcel::getBlobAshmemSize() const
-{
-    // This used to return the size of all blobs that were written to ashmem, now we're returning
-    // the ashmem currently referenced by this Parcel, which should be equivalent.
-    // TODO(b/202029388): Remove method once ABI can be changed.
-    return getOpenAshmemSize();
-}
-
 size_t Parcel::getOpenAshmemSize() const
 {
     auto* kernelFields = maybeKernelFields();
@@ -3331,7 +3331,9 @@
 
 void Parcel::Blob::release() {
     if (mFd != -1 && mData) {
-        ::munmap(mData, mSize);
+        if (::munmap(mData, mSize) == -1) {
+            ALOGW("munmap() failed: %s", strerror(errno));
+        }
     }
     clear();
 }
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index a42ede2..0bec379 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -24,7 +24,6 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/Stability.h>
-#include <cutils/atomic.h>
 #include <utils/AndroidThreads.h>
 #include <utils/String8.h>
 #include <utils/Thread.h>
@@ -49,6 +48,10 @@
 #define DEFAULT_MAX_BINDER_THREADS 15
 #define DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION 1
 
+#if defined(__ANDROID__) || defined(__Fuchsia__)
+#define EXPECT_BINDER_OPEN_SUCCESS
+#endif
+
 #ifdef __ANDROID_VNDK__
 const char* kDefaultDriver = "/dev/vndbinder";
 #else
@@ -57,6 +60,25 @@
 
 // -------------------------------------------------------------------------
 
+namespace {
+bool readDriverFeatureFile(const char* filename) {
+    int fd = open(filename, O_RDONLY | O_CLOEXEC);
+    char on;
+    if (fd == -1) {
+        ALOGE_IF(errno != ENOENT, "%s: cannot open %s: %s", __func__, filename, strerror(errno));
+        return false;
+    }
+    if (read(fd, &on, sizeof(on)) == -1) {
+        ALOGE("%s: error reading to %s: %s", __func__, filename, strerror(errno));
+        close(fd);
+        return false;
+    }
+    close(fd);
+    return on == '1';
+}
+
+} // namespace
+
 namespace android {
 
 using namespace android::binder::impl;
@@ -311,6 +333,7 @@
 sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
 {
     sp<IBinder> result;
+    std::function<void()> postTask;
 
     std::unique_lock<std::mutex> _l(mLock);
 
@@ -358,7 +381,7 @@
                    return nullptr;
             }
 
-            sp<BpBinder> b = BpBinder::PrivateAccessor::create(handle);
+            sp<BpBinder> b = BpBinder::PrivateAccessor::create(handle, &postTask);
             e->binder = b.get();
             if (b) e->refs = b->getWeakRefs();
             result = b;
@@ -371,6 +394,10 @@
         }
     }
 
+    _l.unlock();
+
+    if (postTask) postTask();
+
     return result;
 }
 
@@ -387,7 +414,7 @@
 }
 
 String8 ProcessState::makeBinderThreadName() {
-    int32_t s = android_atomic_add(1, &mThreadPoolSeq);
+    int32_t s = mThreadPoolSeq.fetch_add(1, std::memory_order_release);
     pid_t pid = getpid();
 
     std::string_view driverName = mDriverName.c_str();
@@ -429,8 +456,17 @@
 }
 
 size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
+    // Need to read `mKernelStartedThreads` before `mThreadPoolStarted` (with
+    // non-relaxed memory ordering) to avoid a race like the following:
+    //
+    // thread A: if (mThreadPoolStarted) { // evaluates false
+    // thread B: mThreadPoolStarted = true;
+    // thread B: mKernelStartedThreads++;
+    // thread A: size_t kernelStarted = mKernelStartedThreads;
+    // thread A: LOG_ALWAYS_FATAL_IF(kernelStarted != 0, ...);
+    size_t kernelStarted = mKernelStartedThreads;
+
     if (mThreadPoolStarted) {
-        size_t kernelStarted = mKernelStartedThreads;
         size_t max = mMaxThreads;
         size_t current = mCurrentThreads;
 
@@ -460,7 +496,6 @@
 
     // must not be initialized or maybe has poll thread setup, we
     // currently don't track this in libbinder
-    size_t kernelStarted = mKernelStartedThreads;
     LOG_ALWAYS_FATAL_IF(kernelStarted != 0, "Expecting 0 kernel started threads but have %zu",
                         kernelStarted);
     return mCurrentThreads;
@@ -470,29 +505,37 @@
     return mThreadPoolStarted;
 }
 
+void ProcessState::checkExpectingThreadPoolStart() const {
+    if (mThreadPoolStarted) return;
+
+    // this is also racey, but you should setup the threadpool in the main thread. If that is an
+    // issue, we can check if we are the process leader, but haven't seen the issue in practice.
+    size_t requestedThreads = mMaxThreads.load();
+
+    // if it's manually set to the default, we do ignore it here...
+    if (requestedThreads == DEFAULT_MAX_BINDER_THREADS) return;
+    if (requestedThreads == 0) return;
+
+    ALOGW("Thread pool configuration of size %zu requested, but startThreadPool was not called.",
+          requestedThreads);
+}
+
 #define DRIVER_FEATURES_PATH "/dev/binderfs/features/"
 bool ProcessState::isDriverFeatureEnabled(const DriverFeature feature) {
-    static const char* const names[] = {
-        [static_cast<int>(DriverFeature::ONEWAY_SPAM_DETECTION)] =
-            DRIVER_FEATURES_PATH "oneway_spam_detection",
-        [static_cast<int>(DriverFeature::EXTENDED_ERROR)] =
-            DRIVER_FEATURES_PATH "extended_error",
-    };
-    int fd = open(names[static_cast<int>(feature)], O_RDONLY | O_CLOEXEC);
-    char on;
-    if (fd == -1) {
-        ALOGE_IF(errno != ENOENT, "%s: cannot open %s: %s", __func__,
-                 names[static_cast<int>(feature)], strerror(errno));
-        return false;
+    // Use static variable to cache the results.
+    if (feature == DriverFeature::ONEWAY_SPAM_DETECTION) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "oneway_spam_detection");
+        return enabled;
     }
-    if (read(fd, &on, sizeof(on)) == -1) {
-        ALOGE("%s: error reading to %s: %s", __func__,
-                 names[static_cast<int>(feature)], strerror(errno));
-        close(fd);
-        return false;
+    if (feature == DriverFeature::EXTENDED_ERROR) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "extended_error");
+        return enabled;
     }
-    close(fd);
-    return on == '1';
+    if (feature == DriverFeature::FREEZE_NOTIFICATION) {
+        static bool enabled = readDriverFeatureFile(DRIVER_FEATURES_PATH "freeze_notification");
+        return enabled;
+    }
+    return false;
 }
 
 status_t ProcessState::enableOnewaySpamDetection(bool enable) {
@@ -574,10 +617,10 @@
         }
     }
 
-#ifdef __ANDROID__
+#if defined(EXPECT_BINDER_OPEN_SUCCESS)
     LOG_ALWAYS_FATAL_IF(!opened.ok(),
                         "Binder driver '%s' could not be opened. Error: %s. Terminating.",
-                        error.c_str(), driver);
+                        driver, error.c_str());
 #endif
 
     if (opened.ok()) {
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index b8742af..c7851dc 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -503,7 +503,7 @@
 
                 auto status = binder::os::getRandomBytes(sessionId.data(), sessionId.size());
                 if (status != OK) {
-                    ALOGE("Failed to read random session ID: %s", strerror(-status));
+                    ALOGE("Failed to read random session ID: %s", statusToString(status).c_str());
                     return;
                 }
             } while (server->mSessions.end() != server->mSessions.find(sessionId));
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index 49def82..16023ff 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -164,7 +164,7 @@
         status_t status = mBootstrapTransport->interruptableWriteFully(mShutdownTrigger.get(), &iov,
                                                                        1, std::nullopt, &fds);
         if (status != OK) {
-            ALOGE("Failed to send fd over bootstrap transport: %s", strerror(-status));
+            ALOGE("Failed to send fd over bootstrap transport: %s", statusToString(status).c_str());
             return status;
         }
 
@@ -589,6 +589,21 @@
 status_t RpcSession::setupOneSocketConnection(const RpcSocketAddress& addr,
                                               const std::vector<uint8_t>& sessionId,
                                               bool incoming) {
+    RpcTransportFd transportFd;
+    status_t status = singleSocketConnection(addr, mShutdownTrigger, &transportFd);
+    if (status != OK) return status;
+
+    return initAndAddConnection(std::move(transportFd), sessionId, incoming);
+}
+
+status_t singleSocketConnection(const RpcSocketAddress& addr,
+                                const std::unique_ptr<FdTrigger>& shutdownTrigger,
+                                RpcTransportFd* outFd) {
+    LOG_ALWAYS_FATAL_IF(outFd == nullptr,
+                        "There is no reason to call this function without an outFd");
+    LOG_ALWAYS_FATAL_IF(shutdownTrigger == nullptr,
+                        "FdTrigger argument is required so we don't get stuck in the connect call "
+                        "if the server process shuts down.");
     for (size_t tries = 0; tries < 5; tries++) {
         if (tries > 0) usleep(10000);
 
@@ -620,7 +635,7 @@
             if (connErrno == EAGAIN || connErrno == EINPROGRESS) {
                 // For non-blocking sockets, connect() may return EAGAIN (for unix domain socket) or
                 // EINPROGRESS (for others). Call poll() and getsockopt() to get the error.
-                status_t pollStatus = mShutdownTrigger->triggerablePoll(transportFd, POLLOUT);
+                status_t pollStatus = shutdownTrigger->triggerablePoll(transportFd, POLLOUT);
                 if (pollStatus != OK) {
                     ALOGE("Could not POLLOUT after connect() on non-blocking socket: %s",
                           statusToString(pollStatus).c_str());
@@ -654,7 +669,8 @@
         LOG_RPC_DETAIL("Socket at %s client with fd %d", addr.toString().c_str(),
                        transportFd.fd.get());
 
-        return initAndAddConnection(std::move(transportFd), sessionId, incoming);
+        *outFd = std::move(transportFd);
+        return OK;
     }
 
     ALOGE("Ran out of retries to connect to %s", addr.toString().c_str());
diff --git a/libs/binder/RpcSocketAddress.h b/libs/binder/RpcSocketAddress.h
index c7ba5d9..ee7d448 100644
--- a/libs/binder/RpcSocketAddress.h
+++ b/libs/binder/RpcSocketAddress.h
@@ -113,4 +113,11 @@
     unsigned int mPort;
 };
 
+/**
+ * Connects to a single socket and produces a RpcTransportFd.
+ */
+status_t singleSocketConnection(const RpcSocketAddress& address,
+                                const std::unique_ptr<FdTrigger>& shutdownTrigger,
+                                RpcTransportFd* outFd);
+
 } // namespace android
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index fe6e1a3..03d974d 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -23,6 +23,7 @@
 #include <binder/IPCThreadState.h>
 #include <binder/RpcServer.h>
 
+#include "Constants.h"
 #include "Debug.h"
 #include "RpcWireFormat.h"
 #include "Utils.h"
@@ -337,6 +338,8 @@
 }
 
 RpcState::CommandData::CommandData(size_t size) : mSize(size) {
+    if (size == 0) return;
+
     // The maximum size for regular binder is 1MB for all concurrent
     // transactions. A very small proportion of transactions are even
     // larger than a page, but we need to avoid allocating too much
@@ -348,11 +351,11 @@
     // transaction (in some cases, additional fixed size amounts are added),
     // though for rough consistency, we should avoid cases where this data type
     // is used for multiple dynamic allocations for a single transaction.
-    constexpr size_t kMaxTransactionAllocation = 100 * 1000;
-    if (size == 0) return;
-    if (size > kMaxTransactionAllocation) {
-        ALOGW("Transaction requested too much data allocation %zu", size);
+    if (size > binder::kRpcTransactionLimitBytes) {
+        ALOGE("Transaction requested too much data allocation: %zu bytes, failing.", size);
         return;
+    } else if (size > binder::kLogTransactionsOverBytes) {
+        ALOGW("Transaction too large: inefficient and in danger of breaking: %zu bytes.", size);
     }
     mData.reset(new (std::nothrow) uint8_t[size]);
 }
diff --git a/libs/binder/Status.cpp b/libs/binder/Status.cpp
index dba6587..9a98097 100644
--- a/libs/binder/Status.cpp
+++ b/libs/binder/Status.cpp
@@ -99,27 +99,28 @@
         return status;
     }
 
+    if (mException == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+        status = skipUnusedHeader(parcel);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+        // Read next exception code.
+        status = parcel.readInt32(&mException);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+    }
+
     // Skip over fat response headers.  Not used (or propagated) in native code.
     if (mException == EX_HAS_REPLY_HEADER) {
-        // Note that the header size includes the 4 byte size field.
-        const size_t header_start = parcel.dataPosition();
-        // Get available size before reading more
-        const size_t header_avail = parcel.dataAvail();
-
-        int32_t header_size;
-        status = parcel.readInt32(&header_size);
+        status = skipUnusedHeader(parcel);
         if (status != OK) {
             setFromStatusT(status);
             return status;
         }
 
-        if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
-            android_errorWriteLog(0x534e4554, "132650049");
-            setFromStatusT(UNKNOWN_ERROR);
-            return UNKNOWN_ERROR;
-        }
-
-        parcel.setDataPosition(header_start + header_size);
         // And fat response headers are currently only used when there are no
         // exceptions, so act like there was no error.
         mException = EX_NONE;
@@ -257,5 +258,28 @@
     return ret;
 }
 
+status_t Status::skipUnusedHeader(const Parcel& parcel) {
+    // Note that the header size includes the 4 byte size field.
+    const size_t header_start = parcel.dataPosition();
+    // Get available size before reading more
+    const size_t header_avail = parcel.dataAvail();
+
+    int32_t header_size;
+    status_t status = parcel.readInt32(&header_size);
+    ALOGD("Skip unused header. exception code: %d, start: %zu, size: %d.",
+           mException, header_start, header_size);
+    if (status != OK) {
+        return status;
+    }
+
+    if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
+        android_errorWriteLog(0x534e4554, "132650049");
+        return UNKNOWN_ERROR;
+    }
+
+    parcel.setDataPosition(header_start + header_size);
+    return OK;
+}
+
 }  // namespace binder
 }  // namespace android
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1256173..4332f8a 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -37,6 +37,9 @@
       "name": "binderStabilityTest"
     },
     {
+      "name": "binderStabilityIntegrationTest"
+    },
+    {
       "name": "binderRpcWireProtocolTest"
     },
     {
@@ -67,15 +70,10 @@
       "name": "fuzz_service_test"
     },
     {
-      "name": "CtsOsTestCases",
-      "options": [
-        {
-          "include-filter": "android.os.cts.BinderTest"
-        },
-        {
-          "include-filter": "android.os.cts.ParcelTest"
-        }
-      ]
+      "name": "CtsOsTestCases_ParcelAndBinderTests"
+    },
+    {
+      "name": "FrameworksCoreTests_all_binder"
     },
     {
       "name": "libbinder_rs-internal_test"
@@ -94,6 +92,9 @@
     },
     {
       "name": "libbinder_rpc_unstable_bindgen_test"
+    },
+    {
+      "name": "binderCacheUnitTest"
     }
   ],
   "presubmit-large": [
diff --git a/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl b/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
index 75f8753..9bac386 100644
--- a/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
+++ b/libs/binder/aidl/android/content/pm/ApexStagedEvent.aidl
@@ -16,6 +16,8 @@
 
 package android.content.pm;
 
+import android.content.pm.StagedApexInfo;
+
 /**
  * This event is designed for notification to native code listener about
  * any changes to set of apex packages staged for installation on next boot.
@@ -23,5 +25,5 @@
  * @hide
  */
 parcelable ApexStagedEvent {
-  @utf8InCpp String[] stagedApexModuleNames;
+  StagedApexInfo[] stagedApexInfos;
 }
diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
index 3ddfefa..0f0be2f 100644
--- a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
+++ b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
@@ -135,13 +135,7 @@
     void unregisterStagedApexObserver(in IStagedApexObserver observer);
 
     /**
-     * Get APEX module names of all APEX that are staged ready for installation
+     * Get information of staged APEXes.
      */
-    @utf8InCpp String[] getStagedApexModuleNames();
-
-    /**
-     * Get information of APEX which is staged ready for installation.
-     * Returns null if no such APEX is found.
-     */
-    @nullable StagedApexInfo getStagedApexInfo(in @utf8InCpp String moduleName);
+    StagedApexInfo[] getStagedApexInfos();
 }
diff --git a/libs/binder/aidl/android/content/pm/OWNERS b/libs/binder/aidl/android/content/pm/OWNERS
index 3100518..2617a16 100644
--- a/libs/binder/aidl/android/content/pm/OWNERS
+++ b/libs/binder/aidl/android/content/pm/OWNERS
@@ -1,5 +1,4 @@
+michaelwr@google.com
 narayan@google.com
 patb@google.com
-svetoslavganov@google.com
-toddke@google.com
-patb@google.com
\ No newline at end of file
+schfan@google.com
diff --git a/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl b/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
index 949835b..8f7ad30 100644
--- a/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
+++ b/libs/binder/aidl/android/content/pm/StagedApexInfo.aidl
@@ -22,6 +22,7 @@
  *
  * @hide
  */
+@JavaDerive(equals=true)
 parcelable StagedApexInfo {
   @utf8InCpp String moduleName;
   @utf8InCpp String diskImagePath;
diff --git a/libs/binder/aidl/android/os/IAccessor.aidl b/libs/binder/aidl/android/os/IAccessor.aidl
index a3134a3..c06e05c 100644
--- a/libs/binder/aidl/android/os/IAccessor.aidl
+++ b/libs/binder/aidl/android/os/IAccessor.aidl
@@ -25,15 +25,56 @@
  */
 interface IAccessor {
     /**
+     * The connection info was not available for this service.
+     * This happens when the user-supplied callback fails to produce
+     * valid connection info.
+     * Depending on the implementation of the callback, it might be helpful
+     * to retry.
+     */
+    const int ERROR_CONNECTION_INFO_NOT_FOUND = 0;
+    /**
+     * Failed to create the socket. Often happens when the process trying to create
+     * the socket lacks the permissions to do so.
+     * This may be a temporary issue, so retrying the operation is OK.
+     */
+    const int ERROR_FAILED_TO_CREATE_SOCKET = 1;
+    /**
+     * Failed to connect to the socket. This can happen for many reasons, so be sure
+     * log the error message and check it.
+     * This may be a temporary issue, so retrying the operation is OK.
+     */
+    const int ERROR_FAILED_TO_CONNECT_TO_SOCKET = 2;
+    /**
+     * Failed to connect to the socket with EACCES because this process does not
+     * have perimssions to connect.
+     * There is no need to retry the connection as this access will not be granted
+     * upon retry.
+     */
+    const int ERROR_FAILED_TO_CONNECT_EACCES = 3;
+    /**
+     * Unsupported socket family type returned.
+     * There is no need to retry the connection as this socket family is not
+     * supported.
+     */
+    const int ERROR_UNSUPPORTED_SOCKET_FAMILY = 4;
+
+    /**
      * Adds a connection to the RPC server of the service managed by the IAccessor.
      *
      * This method can be called multiple times to establish multiple distinct
      * connections to the same RPC server.
      *
+     * @throws ServiceSpecificError with message and one of the IAccessor::ERROR_ values.
+     *
      * @return A file descriptor connected to the RPC session of the service managed
      *         by IAccessor.
      */
     ParcelFileDescriptor addConnection();
 
-    // TODO(b/350941051): Add API for debugging.
+    /**
+     * Get the instance name for the service this accessor is responsible for.
+     *
+     * This is used to verify the proxy binder is associated with the expected instance name.
+     */
+    String getInstanceName();
 }
diff --git a/libs/binder/aidl/android/os/IServiceManager.aidl b/libs/binder/aidl/android/os/IServiceManager.aidl
index 1d1f84f..6539238 100644
--- a/libs/binder/aidl/android/os/IServiceManager.aidl
+++ b/libs/binder/aidl/android/os/IServiceManager.aidl
@@ -49,6 +49,8 @@
              DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH
              | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PRIORITY_DEFAULT;
 
+    const int FLAG_IS_LAZY_SERVICE = 1 << 30;
+
     /* Allows services to dump sections in protobuf format. */
     const int DUMP_FLAG_PROTO = 1 << 4;
 
@@ -61,7 +63,8 @@
      *
      * Returns null if the service does not exist.
      *
-     * @deprecated TODO(b/355394904): Use getService2 instead.
+     * @deprecated TODO(b/355394904): Use getService2 instead. This does not return metadata
+     * that is included in ServiceWithMetadata
      */
     @UnsupportedAppUsage
     @nullable IBinder getService(@utf8InCpp String name);
@@ -80,11 +83,20 @@
 
     /**
      * Retrieve an existing service called @a name from the service
+     * manager. Non-blocking. Returns null if the service does not exist.
+     *
+     * @deprecated TODO(b/355394904): Use checkService2 instead. This does not
+     * return metadata that is included in ServiceWithMetadata
+     */
+    @UnsupportedAppUsage
+    @nullable IBinder checkService(@utf8InCpp String name);
+
+    /**
+     * Retrieve an existing service called @a name from the service
      * manager. Non-blocking. Returns null if the service does not
      * exist.
      */
-    @UnsupportedAppUsage
-    Service checkService(@utf8InCpp String name);
+    Service checkService2(@utf8InCpp String name);
 
     /**
      * Place a new @a service called @a name into the service
diff --git a/libs/binder/aidl/android/os/Service.aidl b/libs/binder/aidl/android/os/Service.aidl
index 4c52109..3bc6588 100644
--- a/libs/binder/aidl/android/os/Service.aidl
+++ b/libs/binder/aidl/android/os/Service.aidl
@@ -16,6 +16,8 @@
 
 package android.os;
 
+import android.os.ServiceWithMetadata;
+
 /**
  * Service is a union of different service types that can be returned
  * by the internal {@link ServiceManager#getService(name)} API.
@@ -23,6 +25,6 @@
  * @hide
  */
 union Service {
-    @nullable IBinder binder;
+    ServiceWithMetadata serviceWithMetadata;
     @nullable IBinder accessor;
 }
\ No newline at end of file
diff --git a/libs/binder/aidl/android/os/ServiceWithMetadata.aidl b/libs/binder/aidl/android/os/ServiceWithMetadata.aidl
new file mode 100644
index 0000000..96f76ff
--- /dev/null
+++ b/libs/binder/aidl/android/os/ServiceWithMetadata.aidl
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Service binder with metadata.
+ * @hide
+ */
+parcelable ServiceWithMetadata {
+    /**
+     * IBinder to service
+     */
+    @nullable IBinder service;
+    /**
+     * boolean if the IBinder can be cached by client.
+     */
+    boolean isLazyService;
+}
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index d7f74c4..935bd8d 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -29,6 +29,7 @@
 // ---------------------------------------------------------------------------
 namespace android {
 
+class IPCThreadState;
 class RpcSession;
 class RpcState;
 namespace internal {
@@ -66,6 +67,12 @@
                                                       void* cookie = nullptr, uint32_t flags = 0,
                                                       wp<DeathRecipient>* outRecipient = nullptr);
 
+    [[nodiscard]] status_t addFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& recipient);
+
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& recipient);
+
     LIBBINDER_EXPORTED virtual void* attachObject(const void* objectID, void* object,
                                                   void* cleanupCookie,
                                                   object_cleanup_func func) final;
@@ -75,7 +82,6 @@
     LIBBINDER_EXPORTED sp<IBinder> lookupOrCreateWeak(const void* objectID,
                                                       IBinder::object_make_func make,
                                                       const void* makeArgs);
-
     LIBBINDER_EXPORTED virtual BpBinder* remoteBinder();
 
     LIBBINDER_EXPORTED void sendObituary();
@@ -98,6 +104,7 @@
     // Stop the current recording.
     LIBBINDER_EXPORTED status_t stopRecordingBinder();
 
+    // Note: This class is not thread safe so protect uses of it when necessary
     class ObjectManager {
     public:
         ObjectManager();
@@ -110,8 +117,6 @@
         sp<IBinder> lookupOrCreateWeak(const void* objectID, IBinder::object_make_func make,
                                        const void* makeArgs);
 
-        void kill();
-
     private:
         ObjectManager(const ObjectManager&);
         ObjectManager& operator=(const ObjectManager&);
@@ -132,9 +137,14 @@
         friend class ::android::ProcessState;
         friend class ::android::RpcSession;
         friend class ::android::RpcState;
-        explicit PrivateAccessor(const BpBinder* binder) : mBinder(binder) {}
+        friend class ::android::IPCThreadState;
+        explicit PrivateAccessor(const BpBinder* binder)
+              : mBinder(binder), mMutableBinder(nullptr) {}
+        explicit PrivateAccessor(BpBinder* binder) : mBinder(binder), mMutableBinder(binder) {}
 
-        static sp<BpBinder> create(int32_t handle) { return BpBinder::create(handle); }
+        static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask) {
+            return BpBinder::create(handle, postTask);
+        }
         static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address) {
             return BpBinder::create(session, address);
         }
@@ -146,17 +156,22 @@
         uint64_t rpcAddress() const { return mBinder->rpcAddress(); }
         const sp<RpcSession>& rpcSession() const { return mBinder->rpcSession(); }
 
+        void onFrozenStateChanged(bool isFrozen) { mMutableBinder->onFrozenStateChanged(isFrozen); }
         const BpBinder* mBinder;
+        BpBinder* mMutableBinder;
     };
+
     LIBBINDER_EXPORTED const PrivateAccessor getPrivateAccessor() const {
         return PrivateAccessor(this);
     }
 
+    PrivateAccessor getPrivateAccessor() { return PrivateAccessor(this); }
+
 private:
     friend PrivateAccessor;
     friend class sp<BpBinder>;
 
-    static sp<BpBinder> create(int32_t handle);
+    static sp<BpBinder> create(int32_t handle, std::function<void()>* postTask);
     static sp<BpBinder> create(const sp<RpcSession>& session, uint64_t address);
 
     struct BinderHandle {
@@ -192,6 +207,14 @@
         uint32_t flags;
     };
 
+    void onFrozenStateChanged(bool isFrozen);
+
+    struct FrozenStateChange {
+        bool isFrozen = false;
+        Vector<wp<FrozenStateChangeCallback>> callbacks;
+        bool initialStateReceived = false;
+    };
+
     void reportOneDeath(const Obituary& obit);
     bool isDescriptorCached() const;
 
@@ -199,7 +222,8 @@
     volatile int32_t mAlive;
     volatile int32_t mObitsSent;
     Vector<Obituary>* mObituaries;
-    ObjectManager mObjects;
+    std::unique_ptr<FrozenStateChange> mFrozen;
+    ObjectManager mObjectMgr;
     mutable String16 mDescriptorCache;
     int32_t mTrackedUid;
 
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index 4eb1c08..1ed7c91 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -202,9 +202,18 @@
         virtual void binderDied(const wp<IBinder>& who) = 0;
     };
 
-    #if defined(__clang__)
-    #pragma clang diagnostic pop
-    #endif
+    class FrozenStateChangeCallback : public virtual RefBase {
+    public:
+        enum class State {
+            FROZEN,
+            UNFROZEN,
+        };
+        virtual void onStateChanged(const wp<IBinder>& who, State state) = 0;
+    };
+
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
 
     /**
      * Register the @a recipient for a notification if this binder
@@ -253,6 +262,48 @@
                                             uint32_t flags = 0,
                                             wp<DeathRecipient>* outRecipient = nullptr) = 0;
 
+    /**
+     * addFrozenStateChangeCallback provides a callback mechanism to notify
+     * about process frozen/unfrozen events. Upon registration and any
+     * subsequent state changes, the callback is invoked with the latest process
+     * frozen state.
+     *
+     * If the listener process (the one using this API) is itself frozen, state
+     * change events might be combined into a single one with the latest state.
+     * (meaning 'frozen, unfrozen' might just be 'unfrozen'). This single event
+     * would then be delivered when the listener process becomes unfrozen.
+     * Similarly, if an event happens before the previous event is consumed,
+     * they might be combined. This means the callback might not be called for
+     * every single state change, so don't rely on this API to count how many
+     * times the state has changed.
+     *
+     * @note When all references to the binder are dropped, the callback is
+     * automatically removed. So, you must hold onto a binder in order to
+     * receive notifications about it.
+     *
+     * @note You will only receive freeze notifications for remote binders, as
+     * local binders by definition can't be frozen without you being frozen as
+     * well. Trying to use this function on a local binder will result in an
+     * INVALID_OPERATION code being returned and nothing happening.
+     *
+     * @note This binder always holds a weak reference to the callback.
+     *
+     * @note You will only receive a weak reference to the binder object. You
+     * should not try to promote this to a strong reference. (Nor should you
+     * need to, as there is nothing useful you can directly do with it now that
+     * it has passed on.)
+     */
+    [[nodiscard]] status_t addFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& callback);
+
+    /**
+     * Remove a previously registered freeze callback.
+     * The @a callback will no longer be called if this object
+     * changes its frozen state.
+     */
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(
+            const wp<FrozenStateChangeCallback>& callback);
+
     virtual bool            checkSubclass(const void* subclassID) const;
 
     typedef void (*object_cleanup_func)(const void* id, void* obj, void* cleanupCookie);
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 30e005c..bb45ad2 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -219,9 +219,6 @@
 constexpr const char* const kManualInterfaces[] = {
         "android.app.IActivityManager",
         "android.app.IUidObserver",
-        "android.drm.IDrm",
-        "android.dvr.IVsyncCallback",
-        "android.dvr.IVsyncService",
         "android.gfx.tests.ICallback",
         "android.gfx.tests.IIPCTest",
         "android.gfx.tests.ISafeInterfaceTest",
@@ -235,22 +232,17 @@
         "android.hardware.ICameraClient",
         "android.hardware.ICameraRecordingProxy",
         "android.hardware.ICameraRecordingProxyListener",
-        "android.hardware.ICrypto",
         "android.hardware.IOMXObserver",
         "android.hardware.IStreamListener",
         "android.hardware.IStreamSource",
         "android.media.IAudioService",
         "android.media.IDataSource",
-        "android.media.IDrmClient",
         "android.media.IMediaCodecList",
-        "android.media.IMediaDrmService",
         "android.media.IMediaExtractor",
-        "android.media.IMediaExtractorService",
         "android.media.IMediaHTTPConnection",
         "android.media.IMediaHTTPService",
         "android.media.IMediaLogService",
         "android.media.IMediaMetadataRetriever",
-        "android.media.IMediaMetricsService",
         "android.media.IMediaPlayer",
         "android.media.IMediaPlayerClient",
         "android.media.IMediaPlayerService",
@@ -260,21 +252,13 @@
         "android.media.IMediaSource",
         "android.media.IRemoteDisplay",
         "android.media.IRemoteDisplayClient",
-        "android.media.IResourceManagerClient",
-        "android.media.IResourceManagerService",
-        "android.os.IComplexTypeInterface",
         "android.os.IPermissionController",
-        "android.os.IPingResponder",
         "android.os.IProcessInfoService",
         "android.os.ISchedulingPolicyService",
-        "android.os.IStringConstants",
         "android.os.storage.IObbActionListener",
         "android.os.storage.IStorageEventListener",
         "android.os.storage.IStorageManager",
         "android.os.storage.IStorageShutdownObserver",
-        "android.service.vr.IPersistentVrStateCallbacks",
-        "android.service.vr.IVrManager",
-        "android.service.vr.IVrStateCallbacks",
         "android.ui.ISurfaceComposer",
         "android.utils.IMemory",
         "android.utils.IMemoryHeap",
@@ -286,9 +270,6 @@
         "com.android.internal.os.IShellCallback",
         "drm.IDrmManagerService",
         "drm.IDrmServiceListener",
-        "IAAudioClient",
-        "IAAudioService",
-        "VtsFuzzer",
         nullptr,
 };
 
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 09ab442..9ef4e69 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -174,6 +174,8 @@
     LIBBINDER_EXPORTED static void expungeHandle(int32_t handle, IBinder* binder);
     LIBBINDER_EXPORTED status_t requestDeathNotification(int32_t handle, BpBinder* proxy);
     LIBBINDER_EXPORTED status_t clearDeathNotification(int32_t handle, BpBinder* proxy);
+    [[nodiscard]] status_t addFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
+    [[nodiscard]] status_t removeFrozenStateChangeCallback(int32_t handle, BpBinder* proxy);
 
     LIBBINDER_EXPORTED static void shutdown();
 
@@ -210,13 +212,14 @@
     IPCThreadState();
     ~IPCThreadState();
 
-    status_t sendReply(const Parcel& reply, uint32_t flags);
-    status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
-    status_t talkWithDriver(bool doReceive = true);
-    status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code,
-                                  const Parcel& data, status_t* statusBuffer);
-    status_t getAndExecuteCommand();
-    status_t executeCommand(int32_t command);
+    [[nodiscard]] status_t sendReply(const Parcel& reply, uint32_t flags);
+    [[nodiscard]] status_t waitForResponse(Parcel* reply, status_t* acquireResult = nullptr);
+    [[nodiscard]] status_t talkWithDriver(bool doReceive = true);
+    [[nodiscard]] status_t writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle,
+                                                uint32_t code, const Parcel& data,
+                                                status_t* statusBuffer);
+    [[nodiscard]] status_t getAndExecuteCommand();
+    [[nodiscard]] status_t executeCommand(int32_t command);
     void processPendingDerefs();
     void processPostWriteDerefs();
 
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 5fb7307..d248f22 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -17,14 +17,17 @@
 #pragma once
 #include <binder/Common.h>
 #include <binder/IInterface.h>
-#include <utils/Vector.h>
+// Trusty has its own definition of socket APIs from trusty_ipc.h
+#ifndef __TRUSTY__
+#include <sys/socket.h>
+#endif // __TRUSTY__
 #include <utils/String16.h>
+#include <utils/Vector.h>
 #include <optional>
+#include <set>
 
 namespace android {
 
-// ----------------------------------------------------------------------
-
 /**
  * Service manager for C++ services.
  *
@@ -77,6 +80,14 @@
 
     /**
      * Register a service.
+     *
+     * Note:
+     * This status_t return value may be an exception code from an underlying
+     * Status type that doesn't have a representive error code in
+     * utils/Errors.h.
+     * One example of this is a return value of -7
+     * (Status::Exception::EX_UNSUPPORTED_OPERATION) when the service manager
+     * process is not installed on the device when addService is called.
      */
     // NOLINTNEXTLINE(google-default-arguments)
     virtual status_t addService(const String16& name, const sp<IBinder>& service,
@@ -147,6 +158,12 @@
         int pid;
     };
     virtual std::vector<ServiceDebugInfo> getServiceDebugInfo() = 0;
+
+    /**
+     * Directly enable or disable caching binder during addService calls.
+     * Only used for testing. This is enabled by default.
+     */
+    virtual void enableAddServiceCache(bool value) = 0;
 };
 
 LIBBINDER_EXPORTED sp<IServiceManager> defaultServiceManager();
@@ -216,6 +233,102 @@
 LIBBINDER_EXPORTED bool checkPermission(const String16& permission, pid_t pid, uid_t uid,
                                         bool logPermissionFailure = true);
 
+// ----------------------------------------------------------------------
+// Trusty's definition of the socket APIs does not include sockaddr types
+#ifndef __TRUSTY__
+typedef std::function<status_t(const String16& name, sockaddr* outAddr, socklen_t addrSize)>
+        RpcSocketAddressProvider;
+
+/**
+ * This callback provides a way for clients to get access to remote services by
+ * providing an Accessor object from libbinder that can connect to the remote
+ * service over sockets.
+ *
+ * \param instance name of the service that the callback will provide an
+ *        Accessor for. The provided accessor will be used to set up a client
+ *        RPC connection in libbinder in order to return a binder for the
+ *        associated remote service.
+ *
+ * \return IBinder of the Accessor object that libbinder implements.
+ *         nullptr if the provider callback doesn't know how to reach the
+ *         service or doesn't want to provide access for any other reason.
+ */
+typedef std::function<sp<IBinder>(const String16& instance)> RpcAccessorProvider;
+
+class AccessorProvider;
+
+/**
+ * Register a RpcAccessorProvider for the service manager APIs.
+ *
+ * \param instances that the RpcAccessorProvider knows about and can provide an
+ *        Accessor for.
+ * \param provider callback that generates Accessors.
+ *
+ * \return A pointer used as a recept for the successful addition of the
+ *         AccessorProvider. This is needed to unregister it later.
+ */
+[[nodiscard]] LIBBINDER_EXPORTED std::weak_ptr<AccessorProvider> addAccessorProvider(
+        std::set<std::string>&& instances, RpcAccessorProvider&& providerCallback);
+
+/**
+ * Remove an accessor provider using the pointer provided by addAccessorProvider
+ * along with the cookie pointer that was used.
+ *
+ * \param provider cookie that was returned by addAccessorProvider to keep track
+ *        of this instance.
+ */
+[[nodiscard]] LIBBINDER_EXPORTED status_t
+removeAccessorProvider(std::weak_ptr<AccessorProvider> provider);
+
+/**
+ * Create an Accessor associated with a service that can create a socket connection based
+ * on the connection info from the supplied RpcSocketAddressProvider.
+ *
+ * \param instance name of the service that this Accessor is associated with
+ * \param connectionInfoProvider a callback that returns connection info for
+ *        connecting to the service.
+ * \return the binder of the IAccessor implementation from libbinder
+ */
+LIBBINDER_EXPORTED sp<IBinder> createAccessor(const String16& instance,
+                                              RpcSocketAddressProvider&& connectionInfoProvider);
+
+/**
+ * Check to make sure this binder is the expected binder that is an IAccessor
+ * associated with a specific instance.
+ *
+ * This helper function exists to avoid adding the IAccessor type to
+ * libbinder_ndk.
+ *
+ * \param instance name of the service that this Accessor should be associated with
+ * \param binder to validate
+ *
+ * \return OK if the binder is an IAccessor for `instance`
+ */
+LIBBINDER_EXPORTED status_t validateAccessor(const String16& instance, const sp<IBinder>& binder);
+
+/**
+ * Have libbinder wrap this IAccessor binder in an IAccessorDelegator and return
+ * it.
+ *
+ * This is required only in very specific situations when the process that has
+ * permissions to connect the to RPC service's socket and create the FD for it
+ * is in a separate process from this process that wants to service the Accessor
+ * binder and the communication between these two processes is binder RPC. This
+ * is needed because the binder passed over the binder RPC connection can not be
+ * used as a kernel binder, and needs to be wrapped by a kernel binder that can
+ * then be registered with service manager.
+ *
+ * \param instance name of the Accessor.
+ * \param binder to wrap in a Delegator and register with service manager.
+ * \param outDelegator the wrapped kernel binder for IAccessorDelegator
+ *
+ * \return OK if the binder is an IAccessor for `instance` and the delegator was
+ * successfully created.
+ */
+LIBBINDER_EXPORTED status_t delegateAccessor(const String16& name, const sp<IBinder>& accessor,
+                                             sp<IBinder>* delegator);
+#endif // __TRUSTY__
+
 #ifndef __ANDROID__
 // Create an IServiceManager that delegates the service manager on the device via adb.
 // This is can be set as the default service manager at program start, so that
diff --git a/libs/binder/include/binder/IServiceManagerUnitTestHelper.h b/libs/binder/include/binder/IServiceManagerUnitTestHelper.h
new file mode 100644
index 0000000..ff25163
--- /dev/null
+++ b/libs/binder/include/binder/IServiceManagerUnitTestHelper.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/os/IServiceManager.h>
+#include "IServiceManager.h"
+namespace android {
+
+/**
+ * Encapsulate an AidlServiceManager in a CppBackendShim. Only used for testing.
+ */
+LIBBINDER_EXPORTED sp<IServiceManager> getServiceManagerShimFromAidlServiceManagerForTests(
+        const sp<os::IServiceManager>& sm);
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 5cc0830..9cd2ae9 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -92,7 +92,7 @@
 
     LIBBINDER_EXPORTED status_t appendFrom(const Parcel* parcel, size_t start, size_t len);
 
-    LIBBINDER_EXPORTED int compareData(const Parcel& other);
+    LIBBINDER_EXPORTED int compareData(const Parcel& other) const;
     LIBBINDER_EXPORTED status_t compareDataInRange(size_t thisOffset, const Parcel& other,
                                                    size_t otherOffset, size_t length,
                                                    int* result) const;
@@ -383,11 +383,13 @@
     LIBBINDER_EXPORTED status_t
     writeUniqueFileDescriptorVector(const std::optional<std::vector<binder::unique_fd>>& val);
     LIBBINDER_EXPORTED status_t
-    writeUniqueFileDescriptorVector(const std::unique_ptr<std::vector<binder::unique_fd>>& val)
-            __attribute__((deprecated("use std::optional version instead")));
-    LIBBINDER_EXPORTED status_t
     writeUniqueFileDescriptorVector(const std::vector<binder::unique_fd>& val);
 
+    // WARNING: deprecated and incompatible with AIDL. You should use Parcelable
+    // definitions outside of Parcel to represent shared memory, such as
+    // IMemory or with ParcelFileDescriptor. We should remove this, or move it to be
+    // external to Parcel, it's not a very encapsulated API.
+    //
     // Writes a blob to the parcel.
     // If the blob is small, then it is stored in-place, otherwise it is
     // transferred by way of an anonymous shared memory region.  Prefer sending
@@ -401,8 +403,6 @@
     // as long as it keeps a dup of the blob file descriptor handy for later.
     LIBBINDER_EXPORTED status_t writeDupImmutableBlobFileDescriptor(int fd);
 
-    LIBBINDER_EXPORTED status_t writeObject(const flat_binder_object& val, bool nullMetaData);
-
     // Like Parcel.java's writeNoException().  Just writes a zero int32.
     // Currently the native implementation doesn't do any of the StrictMode
     // stack gathering and serialization that the Java implementation does.
@@ -626,20 +626,19 @@
     LIBBINDER_EXPORTED status_t
     readUniqueFileDescriptorVector(std::optional<std::vector<binder::unique_fd>>* val) const;
     LIBBINDER_EXPORTED status_t
-    readUniqueFileDescriptorVector(std::unique_ptr<std::vector<binder::unique_fd>>* val) const
-            __attribute__((deprecated("use std::optional version instead")));
-    LIBBINDER_EXPORTED status_t
     readUniqueFileDescriptorVector(std::vector<binder::unique_fd>* val) const;
 
+    // WARNING: deprecated and incompatible with AIDL. You should use Parcelable
+    // definitions outside of Parcel to represent shared memory, such as
+    // IMemory or with ParcelFileDescriptor. We should remove this, or move it to be
+    // external to Parcel, it's not a very encapsulated API.
+    //
     // Reads a blob from the parcel.
     // The caller should call release() on the blob after reading its contents.
     LIBBINDER_EXPORTED status_t readBlob(size_t len, ReadableBlob* outBlob) const;
 
     LIBBINDER_EXPORTED const flat_binder_object* readObject(bool nullMetaData) const;
 
-    // Explicitly close all file descriptors in the parcel.
-    LIBBINDER_EXPORTED void closeFileDescriptors();
-
     // Debugging: get metrics on current allocations.
     LIBBINDER_EXPORTED static size_t getGlobalAllocSize();
     LIBBINDER_EXPORTED static size_t getGlobalAllocCount();
@@ -652,6 +651,9 @@
     LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;
 
 private:
+    // Explicitly close all file descriptors in the parcel.
+    void closeFileDescriptors();
+
     // `objects` and `objectsSize` always 0 for RPC Parcels.
     typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                                  size_t objectsSize);
@@ -679,6 +681,7 @@
     // Set the capacity to `desired`, truncating the Parcel if necessary.
     status_t            continueWrite(size_t desired);
     status_t truncateRpcObjects(size_t newObjectsSize);
+    status_t writeObject(const flat_binder_object& val, bool nullMetaData);
     status_t            writePointer(uintptr_t val);
     status_t            readPointer(uintptr_t *pArg) const;
     uintptr_t           readPointer() const;
@@ -1240,7 +1243,7 @@
             if (__builtin_mul_overflow(size, sizeof(T), &dataLen)) {
                 return -EOVERFLOW;
             }
-            auto data = reinterpret_cast<const T*>(readInplace(dataLen));
+            auto data = readInplace(dataLen);
             if (data == nullptr) return BAD_VALUE;
             // std::vector::insert and similar methods will require type-dependent
             // byte alignment when inserting from a const iterator such as `data`,
@@ -1479,14 +1482,15 @@
      * Note: for historical reasons, this does not include ashmem memory which
      * is referenced by this Parcel, but which this parcel doesn't own (e.g.
      * writeFileDescriptor is called without 'takeOwnership' true).
+     *
+     * WARNING: you should not use this, but rather, unparcel, and inspect
+     * each FD independently. This counts ashmem size, but there may be
+     * other resources used for non-ashmem FDs, such as other types of
+     * shared memory, files, etc..
      */
     LIBBINDER_EXPORTED size_t getOpenAshmemSize() const;
 
 private:
-    // TODO(b/202029388): Remove 'getBlobAshmemSize' once no prebuilts reference
-    // this
-    LIBBINDER_EXPORTED size_t getBlobAshmemSize() const;
-
     // Needed so that we can save object metadata to the disk
     friend class android::binder::debug::RecordedTransaction;
 };
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index 021bd58..ced49c1 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -133,6 +133,7 @@
     enum class DriverFeature {
         ONEWAY_SPAM_DETECTION,
         EXTENDED_ERROR,
+        FREEZE_NOTIFICATION,
     };
     // Determine whether a feature is supported by the binder driver.
     LIBBINDER_EXPORTED static bool isDriverFeatureEnabled(const DriverFeature feature);
@@ -140,6 +141,8 @@
 private:
     static sp<ProcessState> init(const char* defaultDriver, bool requireDefault);
 
+    void checkExpectingThreadPoolStart() const;
+
     static void onFork();
     static void parentPostFork();
     static void childPostFork();
@@ -188,8 +191,8 @@
     Vector<handle_entry> mHandleToObject;
 
     bool mForked;
-    bool mThreadPoolStarted;
-    volatile int32_t mThreadPoolSeq;
+    std::atomic_bool mThreadPoolStarted;
+    std::atomic_int32_t mThreadPoolSeq;
 
     CallRestriction mCallRestriction;
 };
diff --git a/libs/binder/include/binder/RpcThreads.h b/libs/binder/include/binder/RpcThreads.h
index 99fa6b8..51b9716b 100644
--- a/libs/binder/include/binder/RpcThreads.h
+++ b/libs/binder/include/binder/RpcThreads.h
@@ -20,6 +20,7 @@
 #include <condition_variable>
 #include <functional>
 #include <memory>
+#include <mutex>
 #include <thread>
 
 #include <binder/Common.h>
diff --git a/libs/binder/include/binder/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h
index c671eed..bcbd14f 100644
--- a/libs/binder/include/binder/SafeInterface.h
+++ b/libs/binder/include/binder/SafeInterface.h
@@ -34,6 +34,13 @@
 namespace android {
 namespace SafeInterface {
 
+/**
+ * WARNING: Prefer to use AIDL-generated interfaces. Using SafeInterface to generate interfaces
+ * does not support tracing, and many other AIDL features out of the box. The general direction
+ * we should go is to migrate safe interface users to AIDL and then remove this so that there
+ * is only one thing to learn/use/test/integrate, not this as well.
+ */
+
 // ParcelHandler is responsible for writing/reading various types to/from a Parcel in a generic way
 class LIBBINDER_EXPORTED ParcelHandler {
 public:
@@ -152,6 +159,14 @@
         return callParcel("writeParcelableVector",
                           [&]() { return parcel->writeParcelableVector(v); });
     }
+
+    status_t read(const Parcel& parcel, std::vector<bool>* v) const {
+        return callParcel("readBoolVector", [&]() { return parcel.readBoolVector(v); });
+    }
+    status_t write(Parcel* parcel, const std::vector<bool>& v) const {
+        return callParcel("writeBoolVector", [&]() { return parcel->writeBoolVector(v); });
+    }
+
     status_t read(const Parcel& parcel, float* f) const {
         return callParcel("readFloat", [&]() { return parcel.readFloat(f); });
     }
diff --git a/libs/binder/include/binder/Stability.h b/libs/binder/include/binder/Stability.h
index cafb8aa..bfe0a5a 100644
--- a/libs/binder/include/binder/Stability.h
+++ b/libs/binder/include/binder/Stability.h
@@ -20,6 +20,8 @@
 #include <binder/IBinder.h>
 #include <string>
 
+class BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
+
 namespace android {
 
 class BpBinder;
@@ -127,6 +129,8 @@
     // through Parcel)
     friend ::android::ProcessState;
 
+    friend ::BinderStabilityIntegrationTest_ExpectedStabilityForItsPartition_Test;
+
     static void tryMarkCompilationUnit(IBinder* binder);
 
     // Currently, we use int16_t for Level so that it can fit in BBinder.
@@ -156,11 +160,11 @@
                                                                 uint32_t flags);
 
     // get stability information as encoded on the wire
-    static int16_t getRepr(IBinder* binder);
+    LIBBINDER_EXPORTED static int16_t getRepr(IBinder* binder);
 
     // whether a transaction on binder is allowed, if the transaction
     // is done from a context with a specific stability level
-    static bool check(int16_t provided, Level required);
+    LIBBINDER_EXPORTED static bool check(int16_t provided, Level required);
 
     static bool isDeclaredLevel(int32_t level);
     static std::string levelString(int32_t level);
diff --git a/libs/binder/include/binder/Status.h b/libs/binder/include/binder/Status.h
index 49ccf7c..d69f662 100644
--- a/libs/binder/include/binder/Status.h
+++ b/libs/binder/include/binder/Status.h
@@ -67,6 +67,9 @@
         EX_SERVICE_SPECIFIC = -8,
         EX_PARCELABLE = -9,
 
+        // See android/os/Parcel.java. We need to handle this in native code.
+        EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127,
+
         // This is special and Java specific; see Parcel.java.
         EX_HAS_REPLY_HEADER = -128,
         // This is special, and indicates to C++ binder proxies that the
@@ -150,6 +153,8 @@
     Status(int32_t exceptionCode, int32_t errorCode);
     Status(int32_t exceptionCode, int32_t errorCode, const String8& message);
 
+    status_t skipUnusedHeader(const Parcel& parcel);
+
     // If |mException| == EX_TRANSACTION_FAILED, generated code will return
     // |mErrorCode| as the result of the transaction rather than write an
     // exception to the reply parcel.
diff --git a/libs/binder/include/binder/Trace.h b/libs/binder/include/binder/Trace.h
index 2f450cb..a3e6c8a 100644
--- a/libs/binder/include/binder/Trace.h
+++ b/libs/binder/include/binder/Trace.h
@@ -42,6 +42,7 @@
 void trace_begin(uint64_t tag, const char* name);
 void trace_end(uint64_t tag);
 void trace_int(uint64_t tag, const char* name, int32_t value);
+uint64_t get_trace_enabled_tags();
 } // namespace os
 
 class LIBBINDER_EXPORTED ScopedTrace {
diff --git a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 392ebb5..48c0ea6 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -37,8 +37,12 @@
 // Set `cid` to VMADDR_CID_LOCAL to only bind to the local vsock interface.
 // Returns an opaque handle to the running server instance, or null if the server
 // could not be started.
+// Set |port| to VMADDR_PORT_ANY to pick an available ephemeral port.
+// |assignedPort| will be set to the assigned port number if it is not null.
+// This will be the provided |port|, or the chosen available ephemeral port when
+// |port| is VMADDR_PORT_ANY.
 [[nodiscard]] ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid,
-                                              unsigned int port);
+                                              unsigned int port, unsigned int* assignedPort);
 
 // Starts a Unix domain RPC server with an open raw socket file descriptor
 // and a given root IBinder object.
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 21537fc..64b1be2 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -23,11 +23,8 @@
 
 #ifndef __TRUSTY__
 #include <cutils/sockets.h>
-#endif
-
-#ifdef __linux__
-#include <linux/vm_sockets.h>
-#endif // __linux__
+#include "vm_sockets.h"
+#endif  // !__TRUSTY__
 
 using android::OK;
 using android::RpcServer;
@@ -81,7 +78,8 @@
 extern "C" {
 
 #ifndef __TRUSTY__
-ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port) {
+ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port,
+                                unsigned int* assignedPort) {
     auto server = RpcServer::make();
 
     unsigned int bindCid = VMADDR_CID_ANY; // bind to the remote interface
@@ -90,7 +88,7 @@
         cid = VMADDR_CID_ANY;       // no need for a connection filter
     }
 
-    if (status_t status = server->setupVsockServer(bindCid, port); status != OK) {
+    if (status_t status = server->setupVsockServer(bindCid, port, assignedPort); status != OK) {
         ALOGE("Failed to set up vsock server with port %u error: %s", port,
               statusToString(status).c_str());
         return nullptr;
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 26c228d..5710bbf 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -82,7 +82,6 @@
 
     llndk: {
         symbol_file: "libbinder_ndk.map.txt",
-        export_llndk_headers: ["libvendorsupport_llndk_headers"],
     },
 
     cflags: [
@@ -95,6 +94,7 @@
         "persistable_bundle.cpp",
         "process.cpp",
         "service_manager.cpp",
+        "binder_rpc.cpp",
     ],
 
     static_libs: [
@@ -109,11 +109,9 @@
     ],
 
     header_libs: [
-        "libvendorsupport_llndk_headers",
         "jni_headers",
     ],
     export_header_lib_headers: [
-        "libvendorsupport_llndk_headers",
         "jni_headers",
     ],
 
@@ -230,12 +228,24 @@
     },
     apex_available: [
         "//apex_available:platform",
+        "//apex_available:anyapex",
         "com.android.media",
         "com.android.media.swcodec",
     ],
     min_sdk_version: "29",
 }
 
+// TODO: if you try to export libbinder_headers_platform_shared from libbinder_ndk.ndk, it will
+// not select the NDK variant of libbinder_headers_platform_shared and instead, it will error
+// that the NDK can't depend on glibc C++.
+cc_library_headers {
+    name: "libbinder_headers_platform_shared_ndk",
+    export_include_dirs: ["include_cpp"],
+    sdk_version: "29",
+    min_sdk_version: "29",
+    visibility: [":__subpackages__"],
+}
+
 ndk_headers {
     name: "libbinder_ndk_headers",
     from: "include_ndk/android",
@@ -246,23 +256,14 @@
     license: "NOTICE",
 }
 
-// TODO(b/160624671): package with the aidl compiler
-ndk_headers {
-    name: "libbinder_ndk_helper_headers",
-    from: "include_cpp/android",
-    to: "android",
-    srcs: [
-        "include_cpp/android/*.h",
-    ],
-    license: "NOTICE",
-}
+// include_cpp are packaged in development/build/sdk.atree with the AIDL compiler
 
 ndk_library {
     name: "libbinder_ndk",
     symbol_file: "libbinder_ndk.map.txt",
     first_version: "29",
     export_header_libs: [
-        "libbinder_ndk_headers",
-        "libbinder_ndk_helper_headers",
+        // used to be part of the NDK, platform things depend on it
+        "libbinder_headers_platform_shared_ndk",
     ],
 }
diff --git a/libs/binder/ndk/binder_rpc.cpp b/libs/binder/ndk/binder_rpc.cpp
new file mode 100644
index 0000000..bb5989d
--- /dev/null
+++ b/libs/binder/ndk/binder_rpc.cpp
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_rpc.h>
+#include <arpa/inet.h>
+#include <binder/IServiceManager.h>
+#include <linux/vm_sockets.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <variant>
+
+#include "ibinder_internal.h"
+#include "status_internal.h"
+
+using ::android::defaultServiceManager;
+using ::android::IBinder;
+using ::android::IServiceManager;
+using ::android::OK;
+using ::android::sp;
+using ::android::status_t;
+using ::android::String16;
+using ::android::String8;
+using ::android::binder::Status;
+
+#define LOG_ACCESSOR_DEBUG(...)
+// #define LOG_ACCESSOR_DEBUG(...) ALOGW(__VA_ARGS__)
+
+struct ABinderRpc_ConnectionInfo {
+    std::variant<sockaddr_vm, sockaddr_un, sockaddr_in> addr;
+};
+
+struct ABinderRpc_Accessor final : public ::android::RefBase {
+    static ABinderRpc_Accessor* make(const char* instance, const sp<IBinder>& binder) {
+        LOG_ALWAYS_FATAL_IF(binder == nullptr, "ABinderRpc_Accessor requires a non-null binder");
+        status_t status = android::validateAccessor(String16(instance), binder);
+        if (status != OK) {
+            ALOGE("The given binder is not a valid IAccessor for %s. Status: %s", instance,
+                  android::statusToString(status).c_str());
+            return nullptr;
+        }
+        return new ABinderRpc_Accessor(binder);
+    }
+
+    sp<IBinder> asBinder() { return mAccessorBinder; }
+
+    ~ABinderRpc_Accessor() { LOG_ACCESSOR_DEBUG("ABinderRpc_Accessor dtor"); }
+
+   private:
+    ABinderRpc_Accessor(sp<IBinder> accessor) : mAccessorBinder(accessor) {}
+    ABinderRpc_Accessor() = delete;
+    sp<IBinder> mAccessorBinder;
+};
+
+struct ABinderRpc_AccessorProvider {
+   public:
+    static ABinderRpc_AccessorProvider* make(std::weak_ptr<android::AccessorProvider> cookie) {
+        if (cookie.expired()) {
+            ALOGE("Null AccessorProvider cookie from libbinder");
+            return nullptr;
+        }
+        return new ABinderRpc_AccessorProvider(cookie);
+    }
+    std::weak_ptr<android::AccessorProvider> mProviderCookie;
+
+   private:
+    ABinderRpc_AccessorProvider() = delete;
+
+    ABinderRpc_AccessorProvider(std::weak_ptr<android::AccessorProvider> provider)
+        : mProviderCookie(provider) {}
+};
+
+struct OnDeleteProviderHolder {
+    OnDeleteProviderHolder(void* data, ABinderRpc_AccessorProviderUserData_deleteCallback onDelete)
+        : mData(data), mOnDelete(onDelete) {}
+    ~OnDeleteProviderHolder() {
+        if (mOnDelete) {
+            mOnDelete(mData);
+        }
+    }
+    void* mData;
+    ABinderRpc_AccessorProviderUserData_deleteCallback mOnDelete;
+    // needs to be copy-able for std::function, but we will never copy it
+    OnDeleteProviderHolder(const OnDeleteProviderHolder&) {
+        LOG_ALWAYS_FATAL("This object can't be copied!");
+    }
+
+   private:
+    OnDeleteProviderHolder() = delete;
+};
+
+ABinderRpc_AccessorProvider* ABinderRpc_registerAccessorProvider(
+        ABinderRpc_AccessorProvider_getAccessorCallback provider,
+        const char* const* const instances, size_t numInstances, void* data,
+        ABinderRpc_AccessorProviderUserData_deleteCallback onDelete) {
+    if (data && onDelete == nullptr) {
+        ALOGE("If a non-null data ptr is passed to ABinderRpc_registerAccessorProvider, then a "
+              "ABinderRpc_AccessorProviderUserData_deleteCallback must also be passed to delete "
+              "the data object once the ABinderRpc_AccessorProvider is removed.");
+        return nullptr;
+    }
+    // call the onDelete when the last reference of this goes away (when the
+    // last reference to the generate std::function goes away).
+    std::shared_ptr<OnDeleteProviderHolder> onDeleteHolder =
+            std::make_shared<OnDeleteProviderHolder>(data, onDelete);
+    if (provider == nullptr) {
+        ALOGE("Null provider passed to ABinderRpc_registerAccessorProvider");
+        return nullptr;
+    }
+    if (numInstances == 0 || instances == nullptr) {
+        ALOGE("No instances passed to ABinderRpc_registerAccessorProvider. numInstances: %zu",
+              numInstances);
+        return nullptr;
+    }
+    std::set<std::string> instanceStrings;
+    for (size_t i = 0; i < numInstances; i++) {
+        instanceStrings.emplace(instances[i]);
+    }
+    android::RpcAccessorProvider generate = [provider,
+                                             onDeleteHolder](const String16& name) -> sp<IBinder> {
+        ABinderRpc_Accessor* accessor = provider(String8(name).c_str(), onDeleteHolder->mData);
+        if (accessor == nullptr) {
+            ALOGE("The supplied ABinderRpc_AccessorProvider_getAccessorCallback returned nullptr");
+            return nullptr;
+        }
+        sp<IBinder> binder = accessor->asBinder();
+        ABinderRpc_Accessor_delete(accessor);
+        return binder;
+    };
+
+    std::weak_ptr<android::AccessorProvider> cookie =
+            android::addAccessorProvider(std::move(instanceStrings), std::move(generate));
+    return ABinderRpc_AccessorProvider::make(cookie);
+}
+
+void ABinderRpc_unregisterAccessorProvider(ABinderRpc_AccessorProvider* provider) {
+    if (provider == nullptr) {
+        LOG_ALWAYS_FATAL("Attempting to remove a null ABinderRpc_AccessorProvider");
+    }
+
+    status_t status = android::removeAccessorProvider(provider->mProviderCookie);
+    // There shouldn't be a way to get here because the caller won't have a
+    // ABinderRpc_AccessorProvider* without calling ABinderRpc_registerAccessorProvider
+    LOG_ALWAYS_FATAL_IF(status == android::BAD_VALUE, "Provider (%p) is not valid. Status: %s",
+                        provider, android::statusToString(status).c_str());
+    LOG_ALWAYS_FATAL_IF(status == android::NAME_NOT_FOUND,
+                        "Provider (%p) was already unregistered. Status: %s", provider,
+                        android::statusToString(status).c_str());
+    LOG_ALWAYS_FATAL_IF(status != OK,
+                        "Unknown error when attempting to unregister ABinderRpc_AccessorProvider "
+                        "(%p). Status: %s",
+                        provider, android::statusToString(status).c_str());
+
+    delete provider;
+}
+
+struct OnDeleteConnectionInfoHolder {
+    OnDeleteConnectionInfoHolder(void* data,
+                                 ABinderRpc_ConnectionInfoProviderUserData_delete onDelete)
+        : mData(data), mOnDelete(onDelete) {}
+    ~OnDeleteConnectionInfoHolder() {
+        if (mOnDelete) {
+            mOnDelete(mData);
+        }
+    }
+    void* mData;
+    ABinderRpc_ConnectionInfoProviderUserData_delete mOnDelete;
+    // needs to be copy-able for std::function, but we will never copy it
+    OnDeleteConnectionInfoHolder(const OnDeleteConnectionInfoHolder&) {
+        LOG_ALWAYS_FATAL("This object can't be copied!");
+    }
+
+   private:
+    OnDeleteConnectionInfoHolder() = delete;
+};
+
+ABinderRpc_Accessor* ABinderRpc_Accessor_new(
+        const char* instance, ABinderRpc_ConnectionInfoProvider provider, void* data,
+        ABinderRpc_ConnectionInfoProviderUserData_delete onDelete) {
+    if (instance == nullptr) {
+        ALOGE("Instance argument must be valid when calling ABinderRpc_Accessor_new");
+        return nullptr;
+    }
+    if (data && onDelete == nullptr) {
+        ALOGE("If a non-null data ptr is passed to ABinderRpc_Accessor_new, then a "
+              "ABinderRpc_ConnectionInfoProviderUserData_delete callback must also be passed to "
+              "delete "
+              "the data object once the ABinderRpc_Accessor is deleted.");
+        return nullptr;
+    }
+    std::shared_ptr<OnDeleteConnectionInfoHolder> onDeleteHolder =
+            std::make_shared<OnDeleteConnectionInfoHolder>(data, onDelete);
+    if (provider == nullptr) {
+        ALOGE("Can't create a new ABinderRpc_Accessor without a ABinderRpc_ConnectionInfoProvider "
+              "and it is "
+              "null");
+        return nullptr;
+    }
+    android::RpcSocketAddressProvider generate = [provider, onDeleteHolder](
+                                                         const String16& name, sockaddr* outAddr,
+                                                         size_t addrLen) -> status_t {
+        std::unique_ptr<ABinderRpc_ConnectionInfo> info(
+                provider(String8(name).c_str(), onDeleteHolder->mData));
+        if (info == nullptr) {
+            ALOGE("The supplied ABinderRpc_ConnectionInfoProvider returned nullptr");
+            return android::NAME_NOT_FOUND;
+        }
+        if (auto addr = std::get_if<sockaddr_vm>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->svm_family != AF_VSOCK,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_vm)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_vm),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            LOG_ACCESSOR_DEBUG(
+                    "Connection info provider found AF_VSOCK. family %d, port %d, cid %d",
+                    addr->svm_family, addr->svm_port, addr->svm_cid);
+            *reinterpret_cast<sockaddr_vm*>(outAddr) = *addr;
+        } else if (auto addr = std::get_if<sockaddr_un>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->sun_family != AF_UNIX,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_un)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_un),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            *reinterpret_cast<sockaddr_un*>(outAddr) = *addr;
+        } else if (auto addr = std::get_if<sockaddr_in>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->sin_family != AF_INET,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_in)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_in),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            *reinterpret_cast<sockaddr_in*>(outAddr) = *addr;
+        } else {
+            LOG_ALWAYS_FATAL(
+                    "Unsupported address family type when trying to get ARpcConnection info. A "
+                    "new variant was added to the ABinderRpc_ConnectionInfo and this needs to be "
+                    "updated.");
+        }
+        return STATUS_OK;
+    };
+    sp<IBinder> accessorBinder = android::createAccessor(String16(instance), std::move(generate));
+    if (accessorBinder == nullptr) {
+        ALOGE("service manager did not get us an accessor");
+        return nullptr;
+    }
+    LOG_ACCESSOR_DEBUG("service manager found an accessor, so returning one now from _new");
+    return ABinderRpc_Accessor::make(instance, accessorBinder);
+}
+
+void ABinderRpc_Accessor_delete(ABinderRpc_Accessor* accessor) {
+    delete accessor;
+}
+
+AIBinder* ABinderRpc_Accessor_asBinder(ABinderRpc_Accessor* accessor) {
+    if (!accessor) {
+        ALOGE("ABinderRpc_Accessor argument is null.");
+        return nullptr;
+    }
+
+    sp<IBinder> binder = accessor->asBinder();
+    sp<AIBinder> aBinder = ABpBinder::lookupOrCreateFromBinder(binder);
+    AIBinder* ptr = aBinder.get();
+    if (ptr == nullptr) {
+        LOG_ALWAYS_FATAL("Failed to lookupOrCreateFromBinder");
+    }
+    ptr->incStrong(nullptr);
+    return ptr;
+}
+
+ABinderRpc_Accessor* ABinderRpc_Accessor_fromBinder(const char* instance, AIBinder* binder) {
+    if (!binder) {
+        ALOGE("binder argument is null");
+        return nullptr;
+    }
+    sp<IBinder> accessorBinder = binder->getBinder();
+    if (accessorBinder) {
+        return ABinderRpc_Accessor::make(instance, accessorBinder);
+    } else {
+        ALOGE("Attempting to get an ABinderRpc_Accessor for %s but AIBinder::getBinder returned "
+              "null",
+              instance);
+        return nullptr;
+    }
+}
+
+binder_status_t ABinderRpc_Accessor_delegateAccessor(const char* instance, AIBinder* accessor,
+                                                     AIBinder** outDelegator) {
+    LOG_ALWAYS_FATAL_IF(outDelegator == nullptr, "The outDelegator argument is null");
+    if (instance == nullptr || accessor == nullptr) {
+        ALOGW("instance or accessor arguments to ABinderRpc_Accessor_delegateBinder are null");
+        *outDelegator = nullptr;
+        return STATUS_UNEXPECTED_NULL;
+    }
+    sp<IBinder> accessorBinder = accessor->getBinder();
+
+    sp<IBinder> delegator;
+    status_t status = android::delegateAccessor(String16(instance), accessorBinder, &delegator);
+    if (status != OK) {
+        return PruneStatusT(status);
+    }
+    sp<AIBinder> binder = ABpBinder::lookupOrCreateFromBinder(delegator);
+    // This AIBinder needs a strong ref to pass ownership to the caller
+    binder->incStrong(nullptr);
+    *outDelegator = binder.get();
+    return STATUS_OK;
+}
+
+ABinderRpc_ConnectionInfo* ABinderRpc_ConnectionInfo_new(const sockaddr* addr, socklen_t len) {
+    if (addr == nullptr || len < 0 || static_cast<size_t>(len) < sizeof(sa_family_t)) {
+        ALOGE("Invalid arguments in ABinderRpc_Connection_new");
+        return nullptr;
+    }
+    // socklen_t was int32_t on 32-bit and uint32_t on 64 bit.
+    size_t socklen = len < 0 || static_cast<uintmax_t>(len) > SIZE_MAX ? 0 : len;
+
+    if (addr->sa_family == AF_VSOCK) {
+        if (len != sizeof(sockaddr_vm)) {
+            ALOGE("Incorrect size of %zu for AF_VSOCK sockaddr_vm. Expecting %zu", socklen,
+                  sizeof(sockaddr_vm));
+            return nullptr;
+        }
+        sockaddr_vm vm = *reinterpret_cast<const sockaddr_vm*>(addr);
+        LOG_ACCESSOR_DEBUG(
+                "ABinderRpc_ConnectionInfo_new found AF_VSOCK. family %d, port %d, cid %d",
+                vm.svm_family, vm.svm_port, vm.svm_cid);
+        return new ABinderRpc_ConnectionInfo(vm);
+    } else if (addr->sa_family == AF_UNIX) {
+        if (len != sizeof(sockaddr_un)) {
+            ALOGE("Incorrect size of %zu for AF_UNIX sockaddr_un. Expecting %zu", socklen,
+                  sizeof(sockaddr_un));
+            return nullptr;
+        }
+        sockaddr_un un = *reinterpret_cast<const sockaddr_un*>(addr);
+        LOG_ACCESSOR_DEBUG("ABinderRpc_ConnectionInfo_new found AF_UNIX. family %d, path %s",
+                           un.sun_family, un.sun_path);
+        return new ABinderRpc_ConnectionInfo(un);
+    } else if (addr->sa_family == AF_INET) {
+        if (len != sizeof(sockaddr_in)) {
+            ALOGE("Incorrect size of %zu for AF_INET sockaddr_in. Expecting %zu", socklen,
+                  sizeof(sockaddr_in));
+            return nullptr;
+        }
+        sockaddr_in in = *reinterpret_cast<const sockaddr_in*>(addr);
+        LOG_ACCESSOR_DEBUG(
+                "ABinderRpc_ConnectionInfo_new found AF_INET. family %d, address %s, port %d",
+                in.sin_family, inet_ntoa(in.sin_addr), ntohs(in.sin_port));
+        return new ABinderRpc_ConnectionInfo(in);
+    }
+
+    ALOGE("ABinderRpc APIs only support AF_VSOCK right now but the supplied sockaddr::sa_family "
+          "is: %hu",
+          addr->sa_family);
+    return nullptr;
+}
+
+void ABinderRpc_ConnectionInfo_delete(ABinderRpc_ConnectionInfo* info) {
+    delete info;
+}
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index cf59420..ff31dd0 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -18,8 +18,10 @@
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_stability.h>
 #include <android/binder_status.h>
+#include <binder/Functional.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
+#include <binder/Trace.h>
 #if __has_include(<private/android_filesystem_config.h>)
 #include <private/android_filesystem_config.h>
 #endif
@@ -40,6 +42,23 @@
 using ::android::String16;
 using ::android::String8;
 using ::android::wp;
+using ::android::binder::impl::make_scope_guard;
+using ::android::binder::impl::scope_guard;
+using ::android::binder::os::get_trace_enabled_tags;
+using ::android::binder::os::trace_begin;
+using ::android::binder::os::trace_end;
+
+// transaction codes for getInterfaceHash and getInterfaceVersion are defined
+// in file : system/tools/aidl/aidl.cpp
+static constexpr int kGetInterfaceVersionId = 0x00fffffe;
+static const char* kInterfaceVersion = "getInterfaceVersion";
+static constexpr int kGetInterfaceHashId = 0x00fffffd;
+static const char* kInterfaceHash = "getInterfaceHash";
+static const char* kNdkTrace = "AIDL::ndk::";
+static const char* kServerTrace = "::server";
+static const char* kClientTrace = "::client";
+static const char* kSeparator = "::";
+static const char* kUnknownCode = "Unknown_Transaction_Code:";
 
 namespace ABBinderTag {
 
@@ -90,6 +109,51 @@
     return sanitized;
 }
 
+const std::string getMethodName(const AIBinder_Class* clazz, transaction_code_t code) {
+    // TODO(b/150155678) - Move getInterfaceHash and getInterfaceVersion to libbinder and remove
+    // hardcoded cases.
+    if (code <= clazz->getTransactionCodeToFunctionLength() && code >= FIRST_CALL_TRANSACTION) {
+        // Codes have FIRST_CALL_TRANSACTION as added offset. Subtract to access function name
+        return clazz->getFunctionName(code);
+    } else if (code == kGetInterfaceVersionId) {
+        return kInterfaceVersion;
+    } else if (code == kGetInterfaceHashId) {
+        return kInterfaceHash;
+    }
+    return kUnknownCode + std::to_string(code);
+}
+
+const std::string getTraceSectionName(const AIBinder_Class* clazz, transaction_code_t code,
+                                      bool isServer) {
+    if (clazz == nullptr) {
+        ALOGE("class associated with binder is null. Class is needed to add trace with interface "
+              "name and function name");
+        return kNdkTrace;
+    }
+
+    const std::string descriptor = clazz->getInterfaceDescriptorUtf8();
+    const std::string methodName = getMethodName(clazz, code);
+
+    size_t traceSize =
+            strlen(kNdkTrace) + descriptor.size() + strlen(kSeparator) + methodName.size();
+    traceSize += isServer ? strlen(kServerTrace) : strlen(kClientTrace);
+
+    std::string trace;
+    // reserve to avoid repeated allocations
+    trace.reserve(traceSize);
+
+    trace += kNdkTrace;
+    trace += clazz->getInterfaceDescriptorUtf8();
+    trace += kSeparator;
+    trace += methodName;
+    trace += isServer ? kServerTrace : kClientTrace;
+
+    LOG_ALWAYS_FATAL_IF(trace.size() != traceSize, "Trace size mismatch. Expected %zu, got %zu",
+                        traceSize, trace.size());
+
+    return trace;
+}
+
 bool AIBinder::associateClass(const AIBinder_Class* clazz) {
     if (clazz == nullptr) return false;
 
@@ -203,6 +267,17 @@
 
 status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parcel* reply,
                               binder_flags_t flags) {
+    std::string sectionName;
+    bool tracingEnabled = get_trace_enabled_tags() & ATRACE_TAG_AIDL;
+    if (tracingEnabled) {
+        sectionName = getTraceSectionName(getClass(), code, true /*isServer*/);
+        trace_begin(ATRACE_TAG_AIDL, sectionName.c_str());
+    }
+
+    scope_guard guard = make_scope_guard([&]() {
+        if (tracingEnabled) trace_end(ATRACE_TAG_AIDL);
+    });
+
     if (isUserCommand(code)) {
         if (getClass()->writeHeader && !data.checkInterface(this)) {
             return STATUS_BAD_TYPE;
@@ -267,11 +342,24 @@
     }
 }
 
+void ABBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                                 void* /* cookie */) {
+    LOG_ALWAYS_FATAL("Should not reach this. Can't linkToDeath local binders.");
+}
+
 ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
     : AIBinder(nullptr /*clazz*/), mRemote(binder) {
     LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
 }
-ABpBinder::~ABpBinder() {}
+
+ABpBinder::~ABpBinder() {
+    for (auto& recip : mDeathRecipients) {
+        sp<AIBinder_DeathRecipient> strongRecip = recip.recipient.promote();
+        if (strongRecip) {
+            strongRecip->pruneThisTransferEntry(getBinder(), recip.cookie);
+        }
+    }
+}
 
 sp<AIBinder> ABpBinder::lookupOrCreateFromBinder(const ::android::sp<::android::IBinder>& binder) {
     if (binder == nullptr) {
@@ -310,6 +398,12 @@
     return ret;
 }
 
+void ABpBinder::addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                  void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.emplace_back(recipient, cookie);
+}
+
 struct AIBinder_Weak {
     wp<AIBinder> binder;
 };
@@ -366,6 +460,31 @@
       mInterfaceDescriptor(interfaceDescriptor),
       mWideInterfaceDescriptor(interfaceDescriptor) {}
 
+bool AIBinder_Class::setTransactionCodeMap(const char** transactionCodeMap, size_t length) {
+    if (mTransactionCodeToFunction != nullptr) {
+        ALOGE("mTransactionCodeToFunction is already set!");
+        return false;
+    }
+    mTransactionCodeToFunction = transactionCodeMap;
+    mTransactionCodeToFunctionLength = length;
+    return true;
+}
+
+const char* AIBinder_Class::getFunctionName(transaction_code_t code) const {
+    if (mTransactionCodeToFunction == nullptr) {
+        ALOGE("mTransactionCodeToFunction is not set!");
+        return nullptr;
+    }
+
+    if (code < FIRST_CALL_TRANSACTION ||
+        code - FIRST_CALL_TRANSACTION >= mTransactionCodeToFunctionLength) {
+        ALOGE("Function name for requested code not found!");
+        return nullptr;
+    }
+
+    return mTransactionCodeToFunction[code - FIRST_CALL_TRANSACTION];
+}
+
 AIBinder_Class* AIBinder_Class_define(const char* interfaceDescriptor,
                                       AIBinder_Class_onCreate onCreate,
                                       AIBinder_Class_onDestroy onDestroy,
@@ -385,6 +504,24 @@
     clazz->onDump = onDump;
 }
 
+void AIBinder_Class_setTransactionCodeToFunctionNameMap(AIBinder_Class* clazz,
+                                                        const char** transactionCodeToFunction,
+                                                        size_t length) {
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr || transactionCodeToFunction == nullptr,
+                        "Valid clazz and transactionCodeToFunction are needed to set code to "
+                        "function mapping.");
+    LOG_ALWAYS_FATAL_IF(!clazz->setTransactionCodeMap(transactionCodeToFunction, length),
+                        "Failed to set transactionCodeToFunction to clazz! Is "
+                        "transactionCodeToFunction already set?");
+}
+
+const char* AIBinder_Class_getFunctionName(AIBinder_Class* clazz, transaction_code_t code) {
+    LOG_ALWAYS_FATAL_IF(
+            clazz == nullptr,
+            "Valid clazz is needed to get function name for requested transaction code");
+    return clazz->getFunctionName(code);
+}
+
 void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) {
     LOG_ALWAYS_FATAL_IF(clazz == nullptr, "disableInterfaceTokenHeader requires non-null clazz");
 
@@ -435,6 +572,17 @@
     LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
 }
 
+void AIBinder_DeathRecipient::pruneThisTransferEntry(const sp<IBinder>& who, void* cookie) {
+    std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
+    mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                                          [&](const sp<TransferDeathRecipient>& tdr) {
+                                              auto tdrWho = tdr->getWho();
+                                              return tdrWho != nullptr && tdrWho.promote() == who &&
+                                                     cookie == tdr->getCookie();
+                                          }),
+                           mDeathRecipients.end());
+}
+
 void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
     mDeathRecipients.erase(std::remove_if(mDeathRecipients.begin(), mDeathRecipients.end(),
                                           [](const sp<TransferDeathRecipient>& tdr) {
@@ -448,6 +596,21 @@
 
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
+    if (mOnUnlinked && cookie &&
+        std::find_if(mDeathRecipients.begin(), mDeathRecipients.end(),
+                     [&cookie](android::sp<TransferDeathRecipient> recipient) {
+                         return recipient->getCookie() == cookie;
+                     }) != mDeathRecipients.end()) {
+        ALOGE("Attempting to AIBinder_linkToDeath with the same cookie with an onUnlink callback. "
+              "This will cause the onUnlinked callback to be called multiple times with the same "
+              "cookie, which is usually not intended.");
+    }
+    if (!mOnUnlinked && cookie) {
+        ALOGW("AIBinder_linkToDeath is being called with a non-null cookie and no onUnlink "
+              "callback set. This might not be intended. AIBinder_DeathRecipient_setOnUnlinked "
+              "should be called first.");
+    }
+
     sp<TransferDeathRecipient> recipient =
             new TransferDeathRecipient(binder, cookie, this, mOnDied, mOnUnlinked);
 
@@ -563,8 +726,11 @@
         return STATUS_UNEXPECTED_NULL;
     }
 
-    // returns binder_status_t
-    return recipient->linkToDeath(binder->getBinder(), cookie);
+    binder_status_t ret = recipient->linkToDeath(binder->getBinder(), cookie);
+    if (ret == STATUS_OK) {
+        binder->addDeathRecipient(recipient, cookie);
+    }
+    return ret;
 }
 
 binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
@@ -686,6 +852,19 @@
 
 binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, AParcel** in,
                                   AParcel** out, binder_flags_t flags) {
+    const AIBinder_Class* clazz = binder ? binder->getClass() : nullptr;
+
+    std::string sectionName;
+    bool tracingEnabled = get_trace_enabled_tags() & ATRACE_TAG_AIDL;
+    if (tracingEnabled) {
+        sectionName = getTraceSectionName(clazz, code, false /*isServer*/);
+        trace_begin(ATRACE_TAG_AIDL, sectionName.c_str());
+    }
+
+    scope_guard guard = make_scope_guard([&]() {
+        if (tracingEnabled) trace_end(ATRACE_TAG_AIDL);
+    });
+
     if (in == nullptr) {
         ALOGE("%s: requires non-null in parameter", __func__);
         return STATUS_UNEXPECTED_NULL;
@@ -824,4 +1003,4 @@
                         "AIBinder_setInheritRt must be called on a local binder");
 
     localBinder->setInheritRt(inheritRt);
-}
+}
\ No newline at end of file
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 9d5368f..a93dc1f 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -51,6 +51,8 @@
         ::android::sp<::android::IBinder> binder = const_cast<AIBinder*>(this)->getBinder();
         return binder->remoteBinder() != nullptr;
     }
+    virtual void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                                   void* cookie) = 0;
 
    private:
     // AIBinder instance is instance of this class for a local object. In order to transact on a
@@ -78,6 +80,8 @@
     ::android::status_t dump(int fd, const ::android::Vector<::android::String16>& args) override;
     ::android::status_t onTransact(uint32_t code, const ::android::Parcel& data,
                                    ::android::Parcel* reply, binder_flags_t flags) override;
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& /* recipient */,
+                           void* /* cookie */) override;
 
    private:
     ABBinder(const AIBinder_Class* clazz, void* userData);
@@ -106,12 +110,20 @@
 
     bool isServiceFuzzing() const { return mServiceFuzzing; }
     void setServiceFuzzing() { mServiceFuzzing = true; }
+    void addDeathRecipient(const ::android::sp<AIBinder_DeathRecipient>& recipient,
+                           void* cookie) override;
 
    private:
     friend android::sp<ABpBinder>;
     explicit ABpBinder(const ::android::sp<::android::IBinder>& binder);
     ::android::sp<::android::IBinder> mRemote;
     bool mServiceFuzzing = false;
+    struct DeathRecipientInfo {
+        android::wp<AIBinder_DeathRecipient> recipient;
+        void* cookie;
+    };
+    std::mutex mDeathRecipientsMutex;
+    std::vector<DeathRecipientInfo> mDeathRecipients;
 };
 
 struct AIBinder_Class {
@@ -120,6 +132,9 @@
 
     const ::android::String16& getInterfaceDescriptor() const { return mWideInterfaceDescriptor; }
     const char* getInterfaceDescriptorUtf8() const { return mInterfaceDescriptor.c_str(); }
+    bool setTransactionCodeMap(const char** transactionCodeMap, size_t transactionCodeMapSize);
+    const char* getFunctionName(transaction_code_t code) const;
+    size_t getTransactionCodeToFunctionLength() const { return mTransactionCodeToFunctionLength; }
 
     // whether a transaction header should be written
     bool writeHeader = true;
@@ -139,6 +154,10 @@
     // This must be a String16 since BBinder virtual getInterfaceDescriptor returns a reference to
     // one.
     const ::android::String16 mWideInterfaceDescriptor;
+    // Array which holds names of the functions
+    const char** mTransactionCodeToFunction = nullptr;
+    // length of mmTransactionCodeToFunctionLength array
+    size_t mTransactionCodeToFunctionLength = 0;
 };
 
 // Ownership is like this (when linked to death):
@@ -183,6 +202,7 @@
     binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie);
     binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie);
     void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked);
+    void pruneThisTransferEntry(const ::android::sp<::android::IBinder>&, void* cookie);
 
    private:
     // When the user of this API deletes a Bp object but not the death recipient, the
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 6273804..c6518d8 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -30,6 +30,15 @@
 #include <android/binder_auto_utils.h>
 #include <android/binder_ibinder.h>
 
+#if defined(__BIONIC__)
+#define API_LEVEL_AT_LEAST(sdk_api_level) __builtin_available(android sdk_api_level, *)
+#elif defined(TRUSTY_USERSPACE)
+// TODO(b/349936395): set to true for Trusty
+#define API_LEVEL_AT_LEAST(sdk_api_level) (false)
+#else
+#define API_LEVEL_AT_LEAST(sdk_api_level) (true)
+#endif  // __BIONIC__
+
 #if __has_include(<android/binder_shell.h>)
 #include <android/binder_shell.h>
 #define HAS_BINDER_SHELL_COMMAND
@@ -164,6 +173,10 @@
      * Helper method to create a class
      */
     static inline AIBinder_Class* defineClass(const char* interfaceDescriptor,
+                                              AIBinder_Class_onTransact onTransact,
+                                              const char** codeToFunction, size_t functionCount);
+
+    static inline AIBinder_Class* defineClass(const char* interfaceDescriptor,
                                               AIBinder_Class_onTransact onTransact);
 
    private:
@@ -225,6 +238,8 @@
 
     SpAIBinder asBinder() override final;
 
+    const SpAIBinder& asBinderReference() { return mBinder; }
+
     bool isRemote() override final { return AIBinder_isRemote(mBinder.get()); }
 
     binder_status_t dump(int fd, const char** args, uint32_t numArgs) override {
@@ -254,6 +269,13 @@
 
 AIBinder_Class* ICInterface::defineClass(const char* interfaceDescriptor,
                                          AIBinder_Class_onTransact onTransact) {
+
+    return defineClass(interfaceDescriptor, onTransact, nullptr, 0);
+}
+
+AIBinder_Class* ICInterface::defineClass(const char* interfaceDescriptor,
+                                         AIBinder_Class_onTransact onTransact,
+                                         const char** codeToFunction, size_t functionCount) {
     AIBinder_Class* clazz = AIBinder_Class_define(interfaceDescriptor, ICInterfaceData::onCreate,
                                                   ICInterfaceData::onDestroy, onTransact);
     if (clazz == nullptr) {
@@ -272,6 +294,18 @@
         AIBinder_Class_setHandleShellCommand(clazz, ICInterfaceData::handleShellCommand);
     }
 #endif
+
+#if defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36
+    if (API_LEVEL_AT_LEAST(36)) {
+        if (codeToFunction != nullptr) {
+            AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction,
+                                                               functionCount);
+        }
+    }
+#else
+    (void)codeToFunction;
+    (void)functionCount;
+#endif  // defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36
     return clazz;
 }
 
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
index d54d62e..f3f3c38 100644
--- a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -22,27 +22,17 @@
 #include <set>
 #include <sstream>
 
-// Include llndk-versioning.h only for vendor build as it is not available for NDK headers.
-#if defined(__ANDROID_VENDOR__)
-#include <android/llndk-versioning.h>
-#elif !defined(API_LEVEL_AT_LEAST)
 #if defined(__BIONIC__)
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
-    (__builtin_available(android sdk_api_level, *))
+#define API_LEVEL_AT_LEAST(sdk_api_level) __builtin_available(android sdk_api_level, *)
+#elif defined(TRUSTY_USERSPACE)
+// TODO(b/349936395): set to true for Trusty
+#define API_LEVEL_AT_LEAST(sdk_api_level) (false)
 #else
-#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
+#define API_LEVEL_AT_LEAST(sdk_api_level) (true)
 #endif  // __BIONIC__
-#endif  // __ANDROID_VENDOR__
 
 namespace aidl::android::os {
 
-#if defined(__ANDROID_VENDOR__)
-#define AT_LEAST_V_OR_202404 constexpr(__ANDROID_VENDOR_API__ >= 202404)
-#else
-// TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
-#define AT_LEAST_V_OR_202404 (__builtin_available(android __ANDROID_API_FUTURE__, *))
-#endif
-
 /**
  * Wrapper class that enables interop with AIDL NDK generation
  * Takes ownership of the APersistableBundle* given to it in reset() and will automatically
@@ -51,7 +41,7 @@
 class PersistableBundle {
    public:
     PersistableBundle() noexcept {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_new();
         }
     }
@@ -61,13 +51,13 @@
     PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle(const PersistableBundle& other) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
     }
     // duplicates, does not take ownership of the APersistableBundle*
     PersistableBundle& operator=(const PersistableBundle& other) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             mPBundle = APersistableBundle_dup(other.mPBundle);
         }
         return *this;
@@ -77,7 +67,7 @@
 
     binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
         reset();
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_readFromParcel(parcel, &mPBundle);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -88,7 +78,7 @@
         if (!mPBundle) {
             return STATUS_BAD_VALUE;
         }
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_writeToParcel(mPBundle, parcel);
         } else {
             return STATUS_INVALID_OPERATION;
@@ -103,7 +93,7 @@
      */
     void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
         if (mPBundle) {
-            if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+            if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
                 APersistableBundle_delete(mPBundle);
             }
             mPBundle = nullptr;
@@ -116,7 +106,7 @@
      * what should be used to check for equality.
      */
     bool deepEquals(const PersistableBundle& rhs) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_isEqual(get(), rhs.get());
         } else {
             return false;
@@ -155,7 +145,7 @@
     inline std::string toString() const {
         if (!mPBundle) {
             return "<PersistableBundle: null>";
-        } else if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        } else if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             std::ostringstream os;
             os << "<PersistableBundle: ";
             os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
@@ -166,7 +156,7 @@
     }
 
     int32_t size() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_size(mPBundle);
         } else {
             return 0;
@@ -174,7 +164,7 @@
     }
 
     int32_t erase(const std::string& key) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_erase(mPBundle, key.c_str());
         } else {
             return 0;
@@ -182,37 +172,37 @@
     }
 
     void putBoolean(const std::string& key, bool val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
         }
     }
 
     void putInt(const std::string& key, int32_t val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putInt(mPBundle, key.c_str(), val);
         }
     }
 
     void putLong(const std::string& key, int64_t val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putLong(mPBundle, key.c_str(), val);
         }
     }
 
     void putDouble(const std::string& key, double val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putDouble(mPBundle, key.c_str(), val);
         }
     }
 
     void putString(const std::string& key, const std::string& val) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
         }
     }
 
     void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             // std::vector<bool> has no ::data().
             int32_t num = vec.size();
             if (num > 0) {
@@ -229,7 +219,7 @@
     }
 
     void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
@@ -237,7 +227,7 @@
         }
     }
     void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
@@ -245,7 +235,7 @@
         }
     }
     void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
@@ -253,7 +243,7 @@
         }
     }
     void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t num = vec.size();
             if (num > 0) {
                 char** inVec = (char**)malloc(num * sizeof(char*));
@@ -268,13 +258,13 @@
         }
     }
     void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
         }
     }
 
     bool getBoolean(const std::string& key, bool* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -282,7 +272,7 @@
     }
 
     bool getInt(const std::string& key, int32_t* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getInt(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -290,7 +280,7 @@
     }
 
     bool getLong(const std::string& key, int64_t* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getLong(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -298,7 +288,7 @@
     }
 
     bool getDouble(const std::string& key, double* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
         } else {
             return false;
@@ -310,7 +300,7 @@
     }
 
     bool getString(const std::string& key, std::string* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             char* outString = nullptr;
             bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
                                                     &stringAllocator, nullptr);
@@ -328,7 +318,7 @@
                                                    const char* _Nonnull, T* _Nullable, int32_t),
                         const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
                         std::vector<T>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t bytes = 0;
             // call first with nullptr to get required size in bytes
             bytes = getVec(pBundle, key, nullptr, 0);
@@ -350,28 +340,28 @@
     }
 
     bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
                                         vec);
         }
         return false;
     }
     bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
                                            vec);
         }
         return false;
     }
     bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
                                           key.c_str(), vec);
         }
@@ -396,7 +386,7 @@
     }
 
     bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
                                                                &stringAllocator, nullptr);
             if (bytes > 0) {
@@ -413,7 +403,7 @@
     }
 
     bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             APersistableBundle* bundle = nullptr;
             bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
             if (ret) {
@@ -445,77 +435,77 @@
     }
 
     std::set<std::string> getBooleanKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getIntKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getLongKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getStringKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getBooleanVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getIntVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getLongVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getDoubleVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getStringVectorKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
         } else {
             return {};
         }
     }
     std::set<std::string> getPersistableBundleKeys() const {
-        if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+        if (API_LEVEL_AT_LEAST(__ANDROID_API_V__)) {
             return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
         } else {
             return {};
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 2929bce..bd46c47 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -219,6 +219,50 @@
 void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) __INTRODUCED_IN(29);
 
 /**
+ * Associates a mapping of transaction codes(transaction_code_t) to function names for the given
+ * class.
+ *
+ * Trace messages will use the provided names instead of bare integer codes when set. If not set by
+ * this function, trace messages will only be identified by the bare code. This should be called one
+ * time during clazz initialization. clazz is defined using AIBinder_Class_define and
+ * transactionCodeToFunctionMap should have same scope as clazz. Resetting/clearing the
+ * transactionCodeToFunctionMap is not allowed. Passing null for either clazz or
+ * transactionCodeToFunctionMap will abort.
+ *
+ * Available since API level 36.
+ *
+ * \param clazz class which should use this transaction to code function map.
+ * \param transactionCodeToFunctionMap array of function names indexed by transaction code.
+ * Transaction codes start from 1, functions with transaction code 1 will correspond to index 0 in
+ * transactionCodeToFunctionMap. When defining methods, transaction codes are expected to be
+ * contiguous, and this is required for maximum memory efficiency.
+ * You can use nullptr if certain transaction codes are not used. Lifetime should be same as clazz.
+ * \param length number of elements in the transactionCodeToFunctionMap
+ */
+void AIBinder_Class_setTransactionCodeToFunctionNameMap(AIBinder_Class* clazz,
+                                                        const char** transactionCodeToFunctionMap,
+                                                        size_t length) __INTRODUCED_IN(36);
+
+/**
+ * Get function name associated with transaction code for given class
+ *
+ * This function returns function name associated with provided transaction code for given class.
+ * AIBinder_Class_setTransactionCodeToFunctionNameMap should be called first to associate function
+ * to transaction code mapping.
+ *
+ * Available since API level 36.
+ *
+ * \param clazz class for which function name is requested
+ * \param transactionCode transaction_code_t for which function name is requested.
+ *
+ * \return function name in form of const char* if transaction code is valid for given class.
+ * The value returned is valid for the lifetime of clazz. if transaction code is invalid or
+ * transactionCodeToFunctionMap is not set, nullptr is returned.
+ */
+const char* AIBinder_Class_getFunctionName(AIBinder_Class* clazz, transaction_code_t code)
+        __INTRODUCED_IN(36);
+
+/**
  * This tells users of this class not to use a transaction header. By default, libbinder_ndk users
  * read/write transaction headers implicitly (in the SDK, this must be manually written by
  * android.os.Parcel#writeInterfaceToken, and it is read/checked with
@@ -771,7 +815,7 @@
  * This provides a per-process-unique total ordering of binders where a null
  * AIBinder* object is considered to be before all other binder objects.
  * For instance, two binders refer to the same object in a local or remote
- * process when both AIBinder_lt(a, b) and AIBinder(b, a) are false. This API
+ * process when both AIBinder_lt(a, b) and AIBinder_lt(b, a) are false. This API
  * might be used to insert and lookup binders in binary search trees.
  *
  * AIBinder* pointers themselves actually also create a per-process-unique total
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
index 5e0d4da..1d516ae 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -17,13 +17,6 @@
 #pragma once
 
 #include <android/binder_parcel.h>
-#if defined(__ANDROID_VENDOR__)
-#include <android/llndk-versioning.h>
-#else
-#if !defined(__INTRODUCED_IN_LLNDK)
-#define __INTRODUCED_IN_LLNDK(level) __attribute__((annotate("introduced_in_llndk=" #level)))
-#endif
-#endif  // __ANDROID_VENDOR__
 #include <stdbool.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
@@ -83,8 +76,7 @@
  *
  * \return Pointer to a new APersistableBundle
  */
-APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Create a new APersistableBundle based off an existing APersistableBundle.
@@ -98,7 +90,7 @@
  * \return Pointer to a new APersistableBundle
  */
 APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Delete an APersistableBundle. This must always be called when finished using
@@ -109,7 +101,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_delete(APersistableBundle* _Nullable pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Check for equality of APersistableBundles.
@@ -123,7 +115,7 @@
  */
 bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
                                 const APersistableBundle* _Nonnull rhs)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Read an APersistableBundle from an AParcel.
@@ -142,7 +134,7 @@
  */
 binder_status_t APersistableBundle_readFromParcel(
         const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Write an APersistableBundle to an AParcel.
@@ -162,7 +154,7 @@
  */
 binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
                                                  AParcel* _Nonnull parcel)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get the size of an APersistableBundle. This is the number of mappings in the
@@ -175,7 +167,7 @@
  * \return number of mappings in the object
  */
 int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Erase any entries added with the provided key.
@@ -188,7 +180,7 @@
  * \return number of entries erased. Either 0 or 1.
  */
 int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a boolean associated with the provided key.
@@ -201,8 +193,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int32_t associated with the provided key.
@@ -215,8 +206,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int64_t associated with the provided key.
@@ -229,8 +219,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a double associated with the provided key.
@@ -243,8 +232,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                  double val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                  double val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a string associated with the provided key.
@@ -258,8 +246,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a boolean vector associated with the provided key.
@@ -275,8 +262,7 @@
  */
 void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
                                          const char* _Nonnull key, const bool* _Nonnull vec,
-                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int32_t vector associated with the provided key.
@@ -292,7 +278,7 @@
  */
 void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
                                      const int32_t* _Nonnull vec, int32_t num)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int64_t vector associated with the provided key.
@@ -308,8 +294,7 @@
  */
 void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
                                       const char* _Nonnull key, const int64_t* _Nonnull vec,
-                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a double vector associated with the provided key.
@@ -325,8 +310,7 @@
  */
 void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key, const double* _Nonnull vec,
-                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a string vector associated with the provided key.
@@ -343,7 +327,7 @@
 void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key,
                                         const char* _Nullable const* _Nullable vec, int32_t num)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an APersistableBundle associated with the provided key.
@@ -359,7 +343,7 @@
 void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
                                              const char* _Nonnull key,
                                              const APersistableBundle* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a boolean associated with the provided key.
@@ -374,7 +358,7 @@
  */
 bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
                                    const char* _Nonnull key, bool* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int32_t associated with the provided key.
@@ -388,8 +372,7 @@
  * \return true if a value exists for the provided key
  */
 bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int64_t associated with the provided key.
@@ -404,7 +387,7 @@
  */
 bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
                                 const char* _Nonnull key, int64_t* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a double associated with the provided key.
@@ -419,7 +402,7 @@
  */
 bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
                                   const char* _Nonnull key, double* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a string associated with the provided key.
@@ -440,8 +423,7 @@
 int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
                                      const char* _Nonnull key, char* _Nullable* _Nonnull val,
                                      APersistableBundle_stringAllocator stringAllocator,
-                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a boolean vector associated with the provided key and place it in the
@@ -468,7 +450,7 @@
 int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
                                             const char* _Nonnull key, bool* _Nullable buffer,
                                             int32_t bufferSizeBytes)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int32_t vector associated with the provided key and place it in the
@@ -494,8 +476,7 @@
  */
 int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key, int32_t* _Nullable buffer,
-                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int64_t vector associated with the provided key and place it in the
@@ -521,8 +502,8 @@
  */
 int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
                                          const char* _Nonnull key, int64_t* _Nullable buffer,
-                                         int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a double vector associated with the provided key and place it in the
@@ -549,7 +530,7 @@
 int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
                                            const char* _Nonnull key, double* _Nullable buffer,
                                            int32_t bufferSizeBytes)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a string vector associated with the provided key and place it in the
@@ -586,7 +567,7 @@
                                            int32_t bufferSizeBytes,
                                            APersistableBundle_stringAllocator stringAllocator,
                                            void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an APersistableBundle* associated with the provided key.
@@ -605,7 +586,7 @@
 bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
                                              const char* _Nonnull key,
                                              APersistableBundle* _Nullable* _Nonnull outBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -638,7 +619,7 @@
                                           int32_t bufferSizeBytes,
                                           APersistableBundle_stringAllocator stringAllocator,
                                           void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -669,8 +650,7 @@
 int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
                                       char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
                                       APersistableBundle_stringAllocator stringAllocator,
-                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -701,8 +681,7 @@
 int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
                                        char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
                                        APersistableBundle_stringAllocator stringAllocator,
-                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -734,8 +713,8 @@
                                          char* _Nullable* _Nullable outKeys,
                                          int32_t bufferSizeBytes,
                                          APersistableBundle_stringAllocator stringAllocator,
-                                         void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -767,8 +746,8 @@
                                          char* _Nullable* _Nullable outKeys,
                                          int32_t bufferSizeBytes,
                                          APersistableBundle_stringAllocator stringAllocator,
-                                         void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -801,7 +780,7 @@
                                                 int32_t bufferSizeBytes,
                                                 APersistableBundle_stringAllocator stringAllocator,
                                                 void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -834,7 +813,7 @@
                                             int32_t bufferSizeBytes,
                                             APersistableBundle_stringAllocator stringAllocator,
                                             void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -867,7 +846,7 @@
                                              int32_t bufferSizeBytes,
                                              APersistableBundle_stringAllocator stringAllocator,
                                              void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -899,7 +878,7 @@
                                                int32_t bufferSizeBytes,
                                                APersistableBundle_stringAllocator stringAllocator,
                                                void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -932,7 +911,7 @@
                                                int32_t bufferSizeBytes,
                                                APersistableBundle_stringAllocator stringAllocator,
                                                void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -963,6 +942,6 @@
 int32_t APersistableBundle_getPersistableBundleKeys(
         const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
         int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
-        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 __END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
index 89f21dd..783e11f 100644
--- a/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
+++ b/libs/binder/ndk/include_platform/android/binder_ibinder_platform.h
@@ -62,6 +62,8 @@
  * This must be called before the object is sent to another process.
  * Aborts on invalid values. Not thread safe.
  *
+ * This overrides the setting in ABinderProcess_disableBackgroundScheduling.
+ *
  * \param binder local server binder to set the policy for
  * \param policy scheduler policy as defined in linux UAPI
  * \param priority priority. [-20..19] for SCHED_NORMAL, [1..99] for RT
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 52edae4..f5df8d5 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -18,7 +18,6 @@
 
 #include <android/binder_ibinder.h>
 #include <android/binder_status.h>
-#include <android/llndk-versioning.h>
 #include <sys/cdefs.h>
 
 __BEGIN_DECLS
@@ -30,7 +29,12 @@
      * Services with methods that perform file IO, web socket creation or ways to egress data must
      * not be added with this flag for privacy concerns.
      */
-    ADD_SERVICE_ALLOW_ISOLATED = 1,
+    ADD_SERVICE_ALLOW_ISOLATED = 1 << 0,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL = 1 << 1,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH = 1 << 2,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL = 1 << 3,
+    ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT = 1 << 4,
+    // All other bits are reserved for internal usage
 };
 
 /**
@@ -253,8 +257,7 @@
  * \return the result of dlopen of the specified HAL
  */
 void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
-                                                 int flag) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                                 int flag) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Prevent lazy services without client from shutting down their process
diff --git a/libs/binder/ndk/include_platform/android/binder_process.h b/libs/binder/ndk/include_platform/android/binder_process.h
index 68528e1..2432099 100644
--- a/libs/binder/ndk/include_platform/android/binder_process.h
+++ b/libs/binder/ndk/include_platform/android/binder_process.h
@@ -47,8 +47,11 @@
  * be called once before startThreadPool. The number of threads can never decrease.
  *
  * This count refers to the number of threads that will be created lazily by the kernel, in
- * addition to the threads created by ABinderProcess_startThreadPool or
- * ABinderProcess_joinThreadPool.
+ * addition to the single threads created by ABinderProcess_startThreadPool (+1) or
+ * ABinderProcess_joinThreadPool (+1). Note: ABinderProcess_startThreadPool starts a thread
+ * itself, but it also enables up to the number of threads passed to this function to start.
+ * This function does not start any threads itself; it only configures
+ * ABinderProcess_startThreadPool.
  *
  * 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.
@@ -63,8 +66,8 @@
 bool ABinderProcess_isThreadPoolStarted(void);
 /**
  * This adds the current thread to the threadpool. This thread will be in addition to the thread
- * started by ABinderProcess_startThreadPool and the lazy kernel-started threads specified by
- * ABinderProcess_setThreadPoolMaxThreadCount.
+ * configured with ABinderProcess_setThreadPoolMaxThreadCount and started with
+ * ABinderProcess_startThreadPool.
  *
  * 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.
@@ -72,6 +75,19 @@
 void ABinderProcess_joinThreadPool(void);
 
 /**
+ * Disables (or enables) background scheduling.
+ *
+ * By default, binder threads execute at a lower priority. However, this can cause
+ * priority inversion, so it is recommended to be disabled in high priority
+ * or in system processes.
+ *
+ * See also AIBinder_setMinSchedulerPolicy, which overrides this setting.
+ *
+ * \param disable whether to disable background scheduling
+ */
+void ABinderProcess_disableBackgroundScheduling(bool disable);
+
+/**
  * This gives you an fd to wait on. Whenever data is available on the fd,
  * ABinderProcess_handlePolledCommands can be called to handle binder queries.
  * This is expected to be used in a single threaded process which waits on
diff --git a/libs/binder/ndk/include_platform/android/binder_rpc.h b/libs/binder/ndk/include_platform/android/binder_rpc.h
new file mode 100644
index 0000000..1318889
--- /dev/null
+++ b/libs/binder/ndk/include_platform/android/binder_rpc.h
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/binder_ibinder.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+/**
+ * @defgroup ABinderRpc Binder RPC
+ *
+ * This set of APIs makes it possible for a process to use the AServiceManager
+ * APIs to get binder objects for services that are available over sockets
+ * instead of the traditional kernel binder with the extra ServiceManager
+ * process.
+ *
+ * These APIs are used to supply libbinder with enough information to create
+ * and manage the socket connections underneath the ServiceManager APIs so the
+ * clients do not need to know the service implementation details or what
+ * transport they use for communication.
+ *
+ * @{
+ */
+
+/**
+ * This represents an IAccessor implementation from libbinder that is
+ * responsible for providing a pre-connected socket file descriptor for a
+ * specific service. The service is an RpcServer and the pre-connected socket is
+ * used to set up a client RpcSession underneath libbinder's IServiceManager APIs
+ * to provide the client with the service's binder for remote communication.
+ */
+typedef struct ABinderRpc_Accessor ABinderRpc_Accessor;
+
+/**
+ * This represents an object that supplies ABinderRpc_Accessors to libbinder
+ * when they are requested. They are requested any time a client is attempting
+ * to get a service through IServiceManager APIs when the services aren't known by
+ * servicemanager.
+ */
+typedef struct ABinderRpc_AccessorProvider ABinderRpc_AccessorProvider;
+
+/**
+ * This represents information necessary for libbinder to be able to connect to a
+ * remote service.
+ * It supports connecting to linux sockets and is created using sockaddr
+ * types for sockets supported by libbinder like sockaddr_in, sockaddr_un,
+ * sockaddr_vm.
+ */
+typedef struct ABinderRpc_ConnectionInfo ABinderRpc_ConnectionInfo;
+
+/**
+ * These APIs provide a way for clients of binder services to be able to get a
+ * binder object of that service through the existing libbinder/libbinder_ndk
+ * Service Manager APIs when that service is using RPC Binder over sockets
+ * instead kernel binder.
+ *
+ * Some of these APIs are used on Android hosts when kernel binder is supported
+ * and the usual servicemanager process is available. Some of these APIs are
+ * only required when there is no kernel binder or extra servicemanager process
+ * such as the case of microdroid or similar VMs.
+ */
+
+/**
+ * This callback is responsible for returning ABinderRpc_Accessor objects for a given
+ * service instance. These ABinderRpc_Accessor objects are implemented by
+ * libbinder_ndk and backed by implementations of android::os::IAccessor in
+ * libbinder.
+ *
+ * \param instance name of the service like
+ *        `android.hardware.vibrator.IVibrator/default`. This string must remain
+ *        valid and unchanged for the duration of this function call.
+ * \param data the data that was associated with this instance when the callback
+ *        was registered.
+ * \return The ABinderRpc_Accessor associated with the service `instance`. This
+ *        callback gives up ownership of the object once it returns it. The
+ *        caller of this callback (libbinder_ndk) is responsible for deleting it
+ *        with ABinderRpc_Accessor_delete.
+ */
+typedef ABinderRpc_Accessor* _Nullable (*ABinderRpc_AccessorProvider_getAccessorCallback)(
+        const char* _Nonnull instance, void* _Nullable data);
+
+/**
+ * This callback is responsible deleting the `void* data` object that is passed
+ * in to ABinderRpc_registerAccessorProvider for the ABinderRpc_AccessorProvider_getAccessorCallback
+ * to use. That object is owned by the ABinderRpc_AccessorProvider and must remain valid for the
+ * lifetime of the callback because it may be called and use the object.
+ * This _delete callback is called after the ABinderRpc_AccessorProvider is remove and
+ * is guaranteed never to be called again.
+ *
+ * \param data a pointer to data that the ABinderRpc_AccessorProvider_getAccessorCallback uses which
+ * is to be deleted by this call.
+ */
+typedef void (*ABinderRpc_AccessorProviderUserData_deleteCallback)(void* _Nullable data);
+
+/**
+ * Inject an ABinderRpc_AccessorProvider_getAccessorCallback into the process for
+ * the Service Manager APIs to use to retrieve ABinderRpc_Accessor objects associated
+ * with different RPC Binder services.
+ *
+ * \param provider callback that returns ABinderRpc_Accessors for libbinder to set up
+ *        RPC clients with.
+ * \param instances array of instances that are supported by this provider. It
+ *        will only be called if the client is looking for an instance that is
+ *        in this list. These instances must be unique per-process. If an
+ *        instance is being registered that was previously registered, this call
+ *        will fail and the ABinderRpc_AccessorProviderUserData_deleteCallback
+ *        will be called to clean up the data.
+ *        This array of strings must remain valid and unchanged for the duration
+ *        of this function call.
+ * \param number of instances in the instances array.
+ * \param data pointer that is passed to the ABinderRpc_AccessorProvider callback.
+ *        IMPORTANT: The ABinderRpc_AccessorProvider now OWNS that object that data
+ *        points to. It can be used as necessary in the callback. The data MUST
+ *        remain valid for the lifetime of the provider callback.
+ *        Do not attempt to give ownership of the same object to different
+ *        providers through multiple calls to this function because the first
+ *        one to be deleted will call the onDelete callback.
+ * \param onDelete callback used to delete the objects that `data` points to.
+ *        This is called after ABinderRpc_AccessorProvider is guaranteed to never be
+ *        called again. Before this callback is called, `data` must remain
+ *        valid.
+ * \return nullptr on error if the data pointer is non-null and the onDelete
+ *         callback is null or if an instance in the instances list was previously
+ *         registered. In the error case of duplicate instances, if data was
+ *         provided with a ABinderRpc_AccessorProviderUserData_deleteCallback,
+ *         the callback will be called to delete the data.
+ *         If nullptr is returned, ABinderRpc_AccessorProviderUserData_deleteCallback
+ *         will be called on data immediately.
+ *         Otherwise returns a pointer to the ABinderRpc_AccessorProvider that
+ *         can be used to remove with ABinderRpc_unregisterAccessorProvider.
+ */
+ABinderRpc_AccessorProvider* _Nullable ABinderRpc_registerAccessorProvider(
+        ABinderRpc_AccessorProvider_getAccessorCallback _Nonnull provider,
+        const char* _Nullable const* const _Nonnull instances, size_t numInstances,
+        void* _Nullable data, ABinderRpc_AccessorProviderUserData_deleteCallback _Nullable onDelete)
+        __INTRODUCED_IN(36);
+
+/**
+ * Remove an ABinderRpc_AccessorProvider from libbinder. This will remove references
+ *        from the ABinderRpc_AccessorProvider and will no longer call the
+ *        ABinderRpc_AccessorProvider_getAccessorCallback.
+ *
+ * Note: The `data` object that was used when adding the accessor will be
+ *       deleted by the ABinderRpc_AccessorProviderUserData_deleteCallback at some
+ *       point after this call. Do not use the object and do not try to delete
+ *       it through any other means.
+ * Note: This will abort when used incorrectly if this provider was never
+ *       registered or if it were already unregistered.
+ *
+ * \param provider to be removed and deleted
+ *
+ */
+void ABinderRpc_unregisterAccessorProvider(ABinderRpc_AccessorProvider* _Nonnull provider)
+        __INTRODUCED_IN(36);
+
+/**
+ * Callback which returns the RPC connection information for libbinder to use to
+ * connect to a socket that a given service is listening on. This is needed to
+ * create an ABinderRpc_Accessor so it can connect to these services.
+ *
+ * \param instance name of the service to connect to. This string must remain
+ *        valid and unchanged for the duration of this function call.
+ * \param data user data for this callback. The pointer is provided in
+ *        ABinderRpc_Accessor_new.
+ * \return ABinderRpc_ConnectionInfo with socket connection information for `instance`
+ */
+typedef ABinderRpc_ConnectionInfo* _Nullable (*ABinderRpc_ConnectionInfoProvider)(
+        const char* _Nonnull instance, void* _Nullable data) __INTRODUCED_IN(36);
+/**
+ * This callback is responsible deleting the `void* data` object that is passed
+ * in to ABinderRpc_Accessor_new for the ABinderRpc_ConnectionInfoProvider to use. That
+ * object is owned by the ABinderRpc_Accessor and must remain valid for the
+ * lifetime the Accessor because it may be used by the connection info provider
+ * callback.
+ * This _delete callback is called after the ABinderRpc_Accessor is removed and
+ * is guaranteed never to be called again.
+ *
+ * \param data a pointer to data that the ABinderRpc_AccessorProvider uses which is to
+ *        be deleted by this call.
+ */
+typedef void (*ABinderRpc_ConnectionInfoProviderUserData_delete)(void* _Nullable data);
+
+/**
+ * Create a new ABinderRpc_Accessor. This creates an IAccessor object in libbinder
+ * that can use the info from the ABinderRpc_ConnectionInfoProvider to connect to a
+ * socket that the service with `instance` name is listening to.
+ *
+ * \param instance name of the service that is listening on the socket. This
+ *        string must remain valid and unchanged for the duration of this
+ *        function call.
+ * \param provider callback that can get the socket connection information for the
+ *           instance. This connection information may be dynamic, so the
+ *           provider will be called any time a new connection is required.
+ * \param data pointer that is passed to the ABinderRpc_ConnectionInfoProvider callback.
+ *        IMPORTANT: The ABinderRpc_ConnectionInfoProvider now OWNS that object that data
+ *        points to. It can be used as necessary in the callback. The data MUST
+ *        remain valid for the lifetime of the provider callback.
+ *        Do not attempt to give ownership of the same object to different
+ *        providers through multiple calls to this function because the first
+ *        one to be deleted will call the onDelete callback.
+ * \param onDelete callback used to delete the objects that `data` points to.
+ *        This is called after ABinderRpc_ConnectionInfoProvider is guaranteed to never be
+ *        called again. Before this callback is called, `data` must remain
+ *        valid.
+ * \return an ABinderRpc_Accessor instance. This is deleted by the caller once it is
+ *         no longer needed.
+ */
+ABinderRpc_Accessor* _Nullable ABinderRpc_Accessor_new(
+        const char* _Nonnull instance, ABinderRpc_ConnectionInfoProvider _Nonnull provider,
+        void* _Nullable data, ABinderRpc_ConnectionInfoProviderUserData_delete _Nullable onDelete)
+        __INTRODUCED_IN(36);
+
+/**
+ * Delete an ABinderRpc_Accessor
+ *
+ * \param accessor to delete
+ */
+void ABinderRpc_Accessor_delete(ABinderRpc_Accessor* _Nonnull accessor) __INTRODUCED_IN(36);
+
+/**
+ * Return the AIBinder associated with an ABinderRpc_Accessor. This can be used to
+ * send the Accessor to another process or even register it with servicemanager.
+ *
+ * \param accessor to get the AIBinder for
+ * \return binder of the supplied accessor with one strong ref count
+ */
+AIBinder* _Nullable ABinderRpc_Accessor_asBinder(ABinderRpc_Accessor* _Nonnull accessor)
+        __INTRODUCED_IN(36);
+
+/**
+ * Return the ABinderRpc_Accessor associated with an AIBinder. The instance must match
+ * the ABinderRpc_Accessor implementation.
+ * This can be used when receiving an AIBinder from another process that the
+ * other process obtained from ABinderRpc_Accessor_asBinder.
+ *
+ * \param instance name of the service that the Accessor is responsible for.
+ *        This string must remain valid and unchanged for the duration of this
+ *        function call.
+ * \param accessorBinder proxy binder from another process's ABinderRpc_Accessor.
+ *        This function preserves the refcount of this binder object and the
+ *        caller still owns it.
+ * \return ABinderRpc_Accessor representing the other processes ABinderRpc_Accessor
+ *         implementation. The caller owns this ABinderRpc_Accessor instance and
+ *         is responsible for deleting it with ABinderRpc_Accessor_delete or
+ *         passing ownership of it elsewhere, like returning it through
+ *         ABinderRpc_AccessorProvider_getAccessorCallback.
+ *         nullptr on error when the accessorBinder is not a valid binder from
+ *         an IAccessor implementation or the IAccessor implementation is not
+ *         associated with the provided instance.
+ */
+ABinderRpc_Accessor* _Nullable ABinderRpc_Accessor_fromBinder(const char* _Nonnull instance,
+                                                              AIBinder* _Nonnull accessorBinder)
+        __INTRODUCED_IN(36);
+
+/**
+ * Wrap an ABinderRpc_Accessor proxy binder with a delegator binder.
+ *
+ * The IAccessorDelegator binder delegates all calls to the proxy binder.
+ *
+ * This is required only in very specific situations when the process that has
+ * permissions to connect the to RPC service's socket and create the FD for it
+ * is in a separate process from this process that wants to serve the Accessor
+ * binder and the communication between these two processes is binder RPC. This
+ * is needed because the binder passed over the binder RPC connection can not be
+ * used as a kernel binder, and needs to be wrapped by a kernel binder that can
+ * then be registered with service manager.
+ *
+ * \param instance name of the service associated with the Accessor
+ * \param binder the AIBinder* from the ABinderRpc_Accessor from the
+ *        ABinderRpc_Accessor_asBinder. The other process across the binder RPC
+ *        connection will have called this and passed the AIBinder* across a
+ *        binder interface to the process calling this function.
+ * \param outDelegator the AIBinder* for the kernel binder that wraps the
+ *        'binder' argument and delegates all calls to it. The caller now owns
+ *        this object with one strong ref count and is responsible for removing
+ *        that ref count with with AIBinder_decStrong when the caller wishes to
+ *        drop the reference.
+ * \return STATUS_OK on success.
+ *         STATUS_UNEXPECTED_NULL if instance or binder arguments are null.
+ *         STATUS_BAD_TYPE if the binder is not an IAccessor.
+ *         STATUS_NAME_NOT_FOUND if the binder is an IAccessor, but not
+ *         associated with the provided instance name.
+ */
+binder_status_t ABinderRpc_Accessor_delegateAccessor(const char* _Nonnull instance,
+                                                     AIBinder* _Nonnull binder,
+                                                     AIBinder* _Nullable* _Nonnull outDelegator)
+        __INTRODUCED_IN(36);
+
+/**
+ * Create a new ABinderRpc_ConnectionInfo with sockaddr. This can be supported socket
+ * types like sockaddr_vm (vsock) and sockaddr_un (Unix Domain Sockets).
+ *
+ * \param addr sockaddr pointer that can come from supported socket
+ *        types like sockaddr_vm (vsock) and sockaddr_un (Unix Domain Sockets).
+ * \param len length of the concrete sockaddr type being used. Like
+ *        sizeof(sockaddr_vm) when sockaddr_vm is used.
+ * \return the connection info based on the given sockaddr
+ */
+ABinderRpc_ConnectionInfo* _Nullable ABinderRpc_ConnectionInfo_new(const sockaddr* _Nonnull addr,
+                                                                   socklen_t len)
+        __INTRODUCED_IN(36);
+
+/**
+ * Delete an ABinderRpc_ConnectionInfo object that was created with
+ * ABinderRpc_ConnectionInfo_new.
+ *
+ * \param info object to be deleted
+ */
+void ABinderRpc_ConnectionInfo_delete(ABinderRpc_ConnectionInfo* _Nonnull info) __INTRODUCED_IN(36);
+
+/** @} */
+
+__END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index 089c775..8050205 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -27,6 +27,10 @@
 
 #if defined(__ANDROID_VENDOR__)
 
+#if defined(__ANDROID_PRODUCT__)
+#error "build bug: product is not part of the vendor half of the Treble system/vendor split"
+#endif
+
 /**
  * Private addition to binder_flag_t.
  */
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 826e199..d4eb8c7 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -164,88 +164,62 @@
 LIBBINDER_NDK35 { # introduced=VanillaIceCream
   global:
     APersistableBundle_readFromParcel;
-    APersistableBundle_readFromParcel; # llndk=202404
     APersistableBundle_writeToParcel;
-    APersistableBundle_writeToParcel; # llndk=202404
     APersistableBundle_new;
-    APersistableBundle_new; # llndk=202404
     APersistableBundle_dup;
-    APersistableBundle_dup; # llndk=202404
     APersistableBundle_delete;
-    APersistableBundle_delete; # llndk=202404
     APersistableBundle_isEqual;
-    APersistableBundle_isEqual; # llndk=202404
     APersistableBundle_size;
-    APersistableBundle_size; # llndk=202404
     APersistableBundle_erase;
-    APersistableBundle_erase; # llndk=202404
     APersistableBundle_putBoolean;
-    APersistableBundle_putBoolean; # llndk=202404
     APersistableBundle_putInt;
-    APersistableBundle_putInt; # llndk=202404
     APersistableBundle_putLong;
-    APersistableBundle_putLong; # llndk=202404
     APersistableBundle_putDouble;
-    APersistableBundle_putDouble; # llndk=202404
     APersistableBundle_putString;
-    APersistableBundle_putString; # llndk=202404
     APersistableBundle_putBooleanVector;
-    APersistableBundle_putBooleanVector; # llndk=202404
     APersistableBundle_putIntVector;
-    APersistableBundle_putIntVector; # llndk=202404
     APersistableBundle_putLongVector;
-    APersistableBundle_putLongVector; # llndk=202404
     APersistableBundle_putDoubleVector;
-    APersistableBundle_putDoubleVector; # llndk=202404
     APersistableBundle_putStringVector;
-    APersistableBundle_putStringVector; # llndk=202404
     APersistableBundle_putPersistableBundle;
-    APersistableBundle_putPersistableBundle; # llndk=202404
     APersistableBundle_getBoolean;
-    APersistableBundle_getBoolean; # llndk=202404
     APersistableBundle_getInt;
-    APersistableBundle_getInt; # llndk=202404
     APersistableBundle_getLong;
-    APersistableBundle_getLong; # llndk=202404
     APersistableBundle_getDouble;
-    APersistableBundle_getDouble; # llndk=202404
     APersistableBundle_getString;
-    APersistableBundle_getString; # llndk=202404
     APersistableBundle_getBooleanVector;
-    APersistableBundle_getBooleanVector; # llndk=202404
     APersistableBundle_getIntVector;
-    APersistableBundle_getIntVector; # llndk=202404
     APersistableBundle_getLongVector;
-    APersistableBundle_getLongVector; # llndk=202404
     APersistableBundle_getDoubleVector;
-    APersistableBundle_getDoubleVector; # llndk=202404
     APersistableBundle_getStringVector;
-    APersistableBundle_getStringVector; # llndk=202404
     APersistableBundle_getPersistableBundle;
-    APersistableBundle_getPersistableBundle; # llndk=202404
     APersistableBundle_getBooleanKeys;
-    APersistableBundle_getBooleanKeys; # llndk=202404
     APersistableBundle_getIntKeys;
-    APersistableBundle_getIntKeys; # llndk=202404
     APersistableBundle_getLongKeys;
-    APersistableBundle_getLongKeys; # llndk=202404
     APersistableBundle_getDoubleKeys;
-    APersistableBundle_getDoubleKeys; # llndk=202404
     APersistableBundle_getStringKeys;
-    APersistableBundle_getStringKeys; # llndk=202404
     APersistableBundle_getBooleanVectorKeys;
-    APersistableBundle_getBooleanVectorKeys; # llndk=202404
     APersistableBundle_getIntVectorKeys;
-    APersistableBundle_getIntVectorKeys; # llndk=202404
     APersistableBundle_getLongVectorKeys;
-    APersistableBundle_getLongVectorKeys; # llndk=202404
     APersistableBundle_getDoubleVectorKeys;
-    APersistableBundle_getDoubleVectorKeys; # llndk=202404
     APersistableBundle_getStringVectorKeys;
-    APersistableBundle_getStringVectorKeys; # llndk=202404
     APersistableBundle_getPersistableBundleKeys;
-    APersistableBundle_getPersistableBundleKeys; # llndk=202404
-    AServiceManager_openDeclaredPassthroughHal; # systemapi llndk=202404
+    AServiceManager_openDeclaredPassthroughHal; # systemapi llndk
+};
+
+LIBBINDER_NDK36 { # introduced=36
+  global:
+    AIBinder_Class_setTransactionCodeToFunctionNameMap;
+    AIBinder_Class_getFunctionName;
+    ABinderRpc_registerAccessorProvider; # systemapi
+    ABinderRpc_unregisterAccessorProvider; # systemapi
+    ABinderRpc_Accessor_new; # systemapi
+    ABinderRpc_Accessor_delegateAccessor; #systemapi
+    ABinderRpc_Accessor_delete; # systemapi
+    ABinderRpc_Accessor_asBinder; # systemapi
+    ABinderRpc_Accessor_fromBinder; # systemapi
+    ABinderRpc_ConnectionInfo_new; # systemapi
+    ABinderRpc_ConnectionInfo_delete; # systemapi
 };
 
 LIBBINDER_NDK_PLATFORM {
@@ -255,6 +229,7 @@
         AIBinder_fromPlatformBinder*;
         AIBinder_toPlatformBinder*;
         AParcel_viewPlatformParcel*;
+        ABinderProcess_disableBackgroundScheduling;
     };
   local:
     *;
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index 0072ac3..bcdb959 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -36,6 +36,10 @@
     IPCThreadState::self()->joinThreadPool();
 }
 
+void ABinderProcess_disableBackgroundScheduling(bool disable) {
+    IPCThreadState::disableBackgroundScheduling(disable);
+}
+
 binder_status_t ABinderProcess_setupPolling(int* fd) {
     return IPCThreadState::self()->setupPolling(fd);
 }
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 259cced..d6ac4ac 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -50,7 +50,25 @@
     sp<IServiceManager> sm = defaultServiceManager();
 
     bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
-    status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
+    int dumpFlags = 0;
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_CRITICAL) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_HIGH) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_HIGH;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_NORMAL) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_NORMAL;
+    }
+    if (flags & AServiceManager_AddServiceFlag::ADD_SERVICE_DUMP_FLAG_PRIORITY_DEFAULT) {
+        dumpFlags |= IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
+    }
+    if (dumpFlags == 0) {
+        dumpFlags = IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT;
+    }
+    status_t exception =
+            sm->addService(String16(instance), binder->getBinder(), allowIsolated, dumpFlags);
+
     return PruneException(exception);
 }
 
diff --git a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
index 66be94f..fb92e05 100644
--- a/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
+++ b/libs/binder/ndk/tests/binderVendorDoubleLoadTest.cpp
@@ -30,6 +30,8 @@
 #include <gtest/gtest.h>
 #include <sys/prctl.h>
 
+static_assert(FLAG_PRIVATE_LOCAL != 0, "Build system configuration breaks stability");
+
 using namespace android;
 using ::android::binder::Status;
 using ::android::internal::Stability;
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index a532ae1..08b857f 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -27,6 +27,7 @@
 
 const char* IFoo::kSomeInstanceName = "libbinder_ndk-test-IFoo";
 const char* IFoo::kInstanceNameToDieFor = "libbinder_ndk-test-IFoo-to-die";
+const char* IFoo::kInstanceNameToDieFor2 = "libbinder_ndk-test-IFoo-to-die2";
 const char* IFoo::kIFooDescriptor = "my-special-IFoo-class";
 
 struct IFoo_Class_Data {
diff --git a/libs/binder/ndk/tests/include/iface/iface.h b/libs/binder/ndk/tests/include/iface/iface.h
index 0a562f0..0cdd50b 100644
--- a/libs/binder/ndk/tests/include/iface/iface.h
+++ b/libs/binder/ndk/tests/include/iface/iface.h
@@ -27,6 +27,7 @@
    public:
     static const char* kSomeInstanceName;
     static const char* kInstanceNameToDieFor;
+    static const char* kInstanceNameToDieFor2;
     static const char* kIFooDescriptor;
 
     static AIBinder_Class* kClass;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 2bc1422..e5a3da4 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -46,6 +46,7 @@
 #include "android/binder_ibinder.h"
 
 using namespace android;
+using namespace std::chrono_literals;
 
 constexpr char kExistingNonNdkService[] = "SurfaceFlinger";
 constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest";
@@ -54,7 +55,7 @@
 constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
 constexpr char kBinderNdkUnitTestServiceFlagged[] = "BinderNdkUnitTestFlagged";
 
-constexpr unsigned int kShutdownWaitTime = 11;
+constexpr auto kShutdownWaitTime = 30s;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
 
 class MyTestFoo : public IFoo {
@@ -253,12 +254,22 @@
 }
 
 bool isServiceRunning(const char* serviceName) {
-    AIBinder* binder = AServiceManager_checkService(serviceName);
-    if (binder == nullptr) {
-        return false;
+    static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+    const Vector<String16> services = sm->listServices();
+    for (const auto service : services) {
+        if (service == String16(serviceName)) return true;
     }
-    AIBinder_decStrong(binder);
+    return false;
+}
 
+bool isServiceShutdownWithWait(const char* serviceName) {
+    LOG(INFO) << "About to check and wait for shutdown of " << std::string(serviceName);
+    const auto before = std::chrono::steady_clock::now();
+    while (isServiceRunning(serviceName)) {
+        sleep(1);
+        const auto after = std::chrono::steady_clock::now();
+        if (after - before >= kShutdownWaitTime) return false;
+    }
     return true;
 }
 
@@ -450,8 +461,8 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
     // Make sure the service is dead after some time of no use
-    sleep(kShutdownWaitTime);
-    ASSERT_EQ(nullptr, AServiceManager_checkService(kLazyBinderNdkUnitTestService));
+    ASSERT_TRUE(isServiceShutdownWithWait(kLazyBinderNdkUnitTestService))
+            << "Service failed to shut down";
 }
 
 TEST(NdkBinder, ForcedPersistenceTest) {
@@ -466,14 +477,12 @@
         service = nullptr;
         IPCThreadState::self()->flushCommands();
 
-        sleep(kShutdownWaitTime);
-
-        bool isRunning = isServiceRunning(kForcePersistNdkUnitTestService);
-
         if (i == 0) {
-            ASSERT_TRUE(isRunning) << "Service shut down when it shouldn't have.";
+            ASSERT_TRUE(isServiceRunning(kForcePersistNdkUnitTestService))
+                    << "Service shut down when it shouldn't have.";
         } else {
-            ASSERT_FALSE(isRunning) << "Service failed to shut down.";
+            ASSERT_TRUE(isServiceShutdownWithWait(kForcePersistNdkUnitTestService))
+                    << "Service failed to shut down";
         }
     }
 }
@@ -491,10 +500,7 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
 
-    LOG(INFO) << "ActiveServicesCallbackTest about to sleep";
-    sleep(kShutdownWaitTime);
-
-    ASSERT_FALSE(isServiceRunning(kActiveServicesNdkUnitTestService))
+    ASSERT_TRUE(isServiceShutdownWithWait(kActiveServicesNdkUnitTestService))
             << "Service failed to shut down.";
 }
 
@@ -536,6 +542,7 @@
     bool deathReceived = false;
 
     std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
         std::cerr << "Binder died (as requested)." << std::endl;
         deathReceived = true;
         deathCv.notify_one();
@@ -547,6 +554,7 @@
     bool wasDeathReceivedFirst = false;
 
     std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
         std::cerr << "Binder unlinked (as requested)." << std::endl;
         wasDeathReceivedFirst = deathReceived;
         unlinkReceived = true;
@@ -560,7 +568,6 @@
 
     EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(cookie)));
 
-    // the binder driver should return this if the service dies during the transaction
     EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
 
     foo = nullptr;
@@ -579,6 +586,173 @@
     binder = nullptr;
 }
 
+TEST(NdkBinder, DeathRecipientDropBinderNoDeath) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    // keep the death recipient around
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    {
+        AIBinder* binder;
+        sp<IFoo> foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+        ASSERT_NE(nullptr, foo.get());
+        ASSERT_NE(nullptr, binder);
+
+        DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+        // let the sp<IFoo> and AIBinder fall out of scope
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+    }
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_FALSE(deathCv.wait_for(lockDeath, 100ms, [&] { return deathReceived; }));
+        EXPECT_FALSE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_FALSE(wasDeathReceivedFirst);
+    }
+}
+
+TEST(NdkBinder, DeathRecipientDropBinderOnDied) {
+    using namespace std::chrono_literals;
+
+    std::mutex deathMutex;
+    std::condition_variable deathCv;
+    bool deathReceived = false;
+
+    sp<IFoo> foo;
+    AIBinder* binder;
+    std::function<void(void)> onDeath = [&] {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        std::cerr << "Binder died (as requested)." << std::endl;
+        deathReceived = true;
+        AIBinder_decStrong(binder);
+        binder = nullptr;
+        deathCv.notify_one();
+    };
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlink);
+
+    foo = IFoo::getService(IFoo::kInstanceNameToDieFor2, &binder);
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+    EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient.get(), static_cast<void*>(cookie)));
+
+    EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
+
+    {
+        std::unique_lock<std::mutex> lockDeath(deathMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; }));
+        EXPECT_TRUE(deathReceived);
+    }
+
+    {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        EXPECT_TRUE(deathCv.wait_for(lockUnlink, 100ms, [&] { return unlinkReceived; }));
+        EXPECT_TRUE(unlinkReceived);
+        EXPECT_TRUE(wasDeathReceivedFirst);
+    }
+}
+
+void LambdaOnUnlinkMultiple(void* cookie) {
+    auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+    (*funcs->onUnlink)();
+}
+
+TEST(NdkBinder, DeathRecipientMultipleLinks) {
+    using namespace std::chrono_literals;
+
+    ndk::SpAIBinder binder;
+    sp<IFoo> foo = IFoo::getService(IFoo::kSomeInstanceName, binder.getR());
+    ASSERT_NE(nullptr, foo.get());
+    ASSERT_NE(nullptr, binder);
+
+    std::function<void(void)> onDeath = [&] {};
+
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    constexpr uint32_t kNumberOfLinksToDeath = 4;
+    uint32_t countdown = kNumberOfLinksToDeath;
+
+    std::function<void(void)> onUnlink = [&] {
+        std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+        countdown--;
+        if (countdown == 0) {
+            unlinkReceived = true;
+            unlinkCv.notify_one();
+        }
+    };
+
+    DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
+
+    ndk::ScopedAIBinder_DeathRecipient recipient(AIBinder_DeathRecipient_new(LambdaOnDeath));
+    AIBinder_DeathRecipient_setOnUnlinked(recipient.get(), LambdaOnUnlinkMultiple);
+
+    for (uint32_t i = 0; i < kNumberOfLinksToDeath; i++) {
+        EXPECT_EQ(STATUS_OK,
+                  AIBinder_linkToDeath(binder.get(), recipient.get(), static_cast<void*>(cookie)));
+    }
+
+    foo = nullptr;
+    binder = nullptr;
+
+    std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+    EXPECT_TRUE(unlinkCv.wait_for(lockUnlink, 5s, [&] { return unlinkReceived; }))
+            << "countdown: " << countdown;
+    EXPECT_TRUE(unlinkReceived);
+    EXPECT_EQ(countdown, 0u);
+}
+
 TEST(NdkBinder, RetrieveNonNdkService) {
     LIBBINDER_IGNORE("-Wdeprecated-declarations")
     AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
@@ -934,6 +1108,37 @@
     EXPECT_EQ(deleteCount, 0);
 }
 
+void* EmptyOnCreate(void* args) {
+    return args;
+}
+void EmptyOnDestroy(void* /*userData*/) {}
+binder_status_t EmptyOnTransact(AIBinder* /*binder*/, transaction_code_t /*code*/,
+                                const AParcel* /*in*/, AParcel* /*out*/) {
+    return STATUS_OK;
+}
+
+TEST(NdkBinder_DeathTest, SetCodeMapTwice) {
+    const char* codeToFunction1[] = {"function-1", "function-2", "function-3"};
+    const char* codeToFunction2[] = {"function-4", "function-5"};
+    const char* interfaceName = "interface_descriptor";
+    AIBinder_Class* clazz =
+            AIBinder_Class_define(interfaceName, EmptyOnCreate, EmptyOnDestroy, EmptyOnTransact);
+    AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction1, 3);
+    // Reset/clear is not allowed
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction2, 2), "");
+}
+
+TEST(NdkBinder_DeathTest, SetNullCodeMap) {
+    const char* codeToFunction[] = {"function-1", "function-2", "function-3"};
+    const char* interfaceName = "interface_descriptor";
+    AIBinder_Class* clazz =
+            AIBinder_Class_define(interfaceName, EmptyOnCreate, EmptyOnDestroy, EmptyOnTransact);
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(nullptr, codeToFunction, 3),
+                 "");
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, nullptr, 0), "");
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(nullptr, nullptr, 0), "");
+}
+
 int main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
 
@@ -943,6 +1148,10 @@
     }
     if (fork() == 0) {
         prctl(PR_SET_PDEATHSIG, SIGHUP);
+        return manualThreadPoolService(IFoo::kInstanceNameToDieFor2);
+    }
+    if (fork() == 0) {
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
         return manualPollingService(IFoo::kSomeInstanceName);
     }
     if (fork() == 0) {
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 2deb254..adef9ea 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -15,6 +15,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libzerocopy",
     ],
     host_supported: true,
     vendor_available: true,
@@ -79,6 +81,9 @@
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -129,9 +134,21 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    bindgen_flags: [
+        "--blocklist-type",
+        "sockaddr",
+        "--raw-line",
+        "use libc::sockaddr;",
+    ],
+    cflags: [
+        "-DANDROID_PLATFORM",
+    ],
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -166,6 +183,9 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    cflags: [
+        "-DANDROID_PLATFORM",
+    ],
     shared_libs: [
         "libbinder_ndk_on_trusty_mock",
         "libc++",
@@ -185,6 +205,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libzerocopy",
     ],
 }
 
@@ -196,4 +218,7 @@
     auto_gen_config: true,
     clippy_lints: "none",
     lints: "none",
+    rustlibs: [
+        "liblibc",
+    ],
 }
diff --git a/libs/binder/rust/Cargo.toml b/libs/binder/rust/Cargo.toml
new file mode 100644
index 0000000..e5738c5
--- /dev/null
+++ b/libs/binder/rust/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "android-binder"
+version = "0.1.0"
+edition = "2021"
+description = "Safe bindings to Android Binder, restricted to the NDK"
+license = "Apache-2.0"
+
+[dependencies]
+binder-ndk-sys = { package = "android-binder-ndk-sys", version = "0.1", path = "./sys" }
+downcast-rs = "1.2.1"
+libc = "0.2.159"
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = ["cfg(android_vendor)", "cfg(android_ndk)", "cfg(android_vndk)", "cfg(trusty)"]
diff --git a/libs/binder/rust/build.rs b/libs/binder/rust/build.rs
new file mode 100644
index 0000000..f3e6b53
--- /dev/null
+++ b/libs/binder/rust/build.rs
@@ -0,0 +1,4 @@
+fn main() {
+    // Anything with cargo is NDK only. If you want to access anything else, use Soong.
+    println!("cargo::rustc-cfg=android_ndk");
+}
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 2e46345..46651ce 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -26,12 +26,13 @@
     ],
     visibility: [
         "//device/google/cuttlefish/shared/minidroid/sample",
+        "//hardware/interfaces/security/see:__subpackages__",
         "//packages/modules/Virtualization:__subpackages__",
-        "//system/software_defined_vehicle:__subpackages__",
     ],
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.microfuchsia",
         "com.android.uwb",
         "com.android.virt",
     ],
@@ -60,6 +61,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.microfuchsia",
         "com.android.uwb",
         "com.android.virt",
     ],
@@ -93,6 +95,7 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.compos",
+        "com.android.microfuchsia",
         "com.android.uwb",
         "com.android.virt",
     ],
diff --git a/libs/binder/rust/rpcbinder/src/server/android.rs b/libs/binder/rust/rpcbinder/src/server/android.rs
index 2ab3447..74ce315 100644
--- a/libs/binder/rust/rpcbinder/src/server/android.rs
+++ b/libs/binder/rust/rpcbinder/src/server/android.rs
@@ -18,7 +18,7 @@
 use binder::{unstable_api::AsNative, SpIBinder};
 use binder_rpc_unstable_bindgen::ARpcServer;
 use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
+use std::ffi::{c_uint, CString};
 use std::io::{Error, ErrorKind};
 use std::os::unix::io::{IntoRawFd, OwnedFd};
 
@@ -42,18 +42,29 @@
     /// Creates a binder RPC server, serving the supplied binder service implementation on the given
     /// vsock port. Only connections from the given CID are accepted.
     ///
-    // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
-    // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
-    pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
+    /// Set `cid` to [`libc::VMADDR_CID_ANY`] to accept connections from any client.
+    /// Set `cid` to [`libc::VMADDR_CID_LOCAL`] to only bind to the local vsock interface.
+    /// Set `port` to [`libc::VMADDR_PORT_ANY`] to pick an ephemeral port.
+    /// The assigned port is returned with RpcServer.
+    pub fn new_vsock(
+        mut service: SpIBinder,
+        cid: u32,
+        port: u32,
+    ) -> Result<(RpcServer, u32 /* assigned_port */), Error> {
         let service = service.as_native_mut();
 
+        let mut assigned_port: c_uint = 0;
         // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
         // Plus the binder objects are threadsafe.
-        unsafe {
+        let server = unsafe {
             Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
-                service, cid, port,
-            ))
-        }
+                service,
+                cid,
+                port,
+                &mut assigned_port,
+            ))?
+        };
+        Ok((server, assigned_port as _))
     }
 
     /// Creates a binder RPC server, serving the supplied binder service implementation on the given
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 9a252b8..6a8a698 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -136,6 +136,31 @@
     }
 }
 
+/// Same as `Stability`, but in the form of a trait. Used when the stability should be encoded in
+/// the type.
+///
+/// When/if the `adt_const_params` Rust feature is stabilized, this could be replace by using
+/// `Stability` directly with const generics.
+pub trait StabilityType {
+    /// The `Stability` represented by this type.
+    const VALUE: Stability;
+}
+
+/// `Stability::Local`.
+#[derive(Debug)]
+pub enum LocalStabilityType {}
+/// `Stability::Vintf`.
+#[derive(Debug)]
+pub enum VintfStabilityType {}
+
+impl StabilityType for LocalStabilityType {
+    const VALUE: Stability = Stability::Local;
+}
+
+impl StabilityType for VintfStabilityType {
+    const VALUE: Stability = Stability::Vintf;
+}
+
 /// A local service that can be remotable via Binder.
 ///
 /// An object that implement this interface made be made into a Binder service
@@ -182,8 +207,10 @@
 /// Corresponds to TF_ONE_WAY -- an asynchronous call.
 pub const FLAG_ONEWAY: TransactionFlags = sys::FLAG_ONEWAY;
 /// Corresponds to TF_CLEAR_BUF -- clear transaction buffers after call is made.
+#[cfg(not(android_ndk))]
 pub const FLAG_CLEAR_BUF: TransactionFlags = sys::FLAG_CLEAR_BUF;
 /// Set to the vendor flag if we are building for the VNDK, 0 otherwise
+#[cfg(not(android_ndk))]
 pub const FLAG_PRIVATE_LOCAL: TransactionFlags = sys::FLAG_PRIVATE_LOCAL;
 
 /// Internal interface of binder local or remote objects for making
@@ -196,7 +223,7 @@
     fn is_binder_alive(&self) -> bool;
 
     /// Indicate that the service intends to receive caller security contexts.
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     fn set_requesting_sid(&mut self, enable: bool);
 
     /// Dump this object to the given file handle
@@ -321,7 +348,6 @@
                 panic!("Expected non-null class pointer from AIBinder_Class_define!");
             }
             sys::AIBinder_Class_setOnDump(class, Some(I::on_dump));
-            sys::AIBinder_Class_setHandleShellCommand(class, None);
             class
         };
         InterfaceClass(ptr)
@@ -689,7 +715,7 @@
 pub struct BinderFeatures {
     /// Indicates that the service intends to receive caller security contexts. This must be true
     /// for `ThreadState::with_calling_sid` to work.
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     pub set_requesting_sid: bool,
     // Ensure that clients include a ..BinderFeatures::default() to preserve backwards compatibility
     // when new fields are added. #[non_exhaustive] doesn't work because it prevents struct
@@ -891,8 +917,12 @@
         impl $native {
             /// Create a new binder service.
             pub fn new_binder<T: $interface + Sync + Send + 'static>(inner: T, features: $crate::BinderFeatures) -> $crate::Strong<dyn $interface> {
+                #[cfg(not(android_ndk))]
                 let mut binder = $crate::binder_impl::Binder::new_with_stability($native(Box::new(inner)), $stability);
-                #[cfg(not(android_vndk))]
+                #[cfg(android_ndk)]
+                let mut binder = $crate::binder_impl::Binder::new($native(Box::new(inner)));
+
+                #[cfg(not(any(android_vndk, android_ndk)))]
                 $crate::binder_impl::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
                 $crate::Strong::new(Box::new(binder))
             }
@@ -1171,5 +1201,45 @@
                 Ok(v.map(|v| v.into_iter().map(Self).collect()))
             }
         }
+
+        impl std::ops::BitOr for $enum {
+            type Output = Self;
+            fn bitor(self, rhs: Self) -> Self {
+                Self(self.0 | rhs.0)
+            }
+        }
+
+        impl std::ops::BitOrAssign for $enum {
+            fn bitor_assign(&mut self, rhs: Self) {
+                self.0 = self.0 | rhs.0;
+            }
+        }
+
+        impl std::ops::BitAnd for $enum {
+            type Output = Self;
+            fn bitand(self, rhs: Self) -> Self {
+                Self(self.0 & rhs.0)
+            }
+        }
+
+        impl std::ops::BitAndAssign for $enum {
+            fn bitand_assign(&mut self, rhs: Self) {
+                self.0 = self.0 & rhs.0;
+            }
+        }
+
+        impl std::ops::BitXor for $enum {
+            type Output = Self;
+            fn bitxor(self, rhs: Self) -> Self {
+                Self(self.0 ^ rhs.0)
+            }
+        }
+
+        impl std::ops::BitXorAssign for $enum {
+            fn bitxor_assign(&mut self, rhs: Self) {
+                self.0 = self.0 ^ rhs.0;
+            }
+        }
+
     };
 }
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index e70f4f0..0026f21 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -99,11 +99,15 @@
 mod error;
 mod native;
 mod parcel;
+#[cfg(not(trusty))]
+mod persistable_bundle;
 mod proxy;
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
 mod service;
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
 mod state;
+#[cfg(not(any(android_vendor, android_ndk, android_vndk, trusty)))]
+mod system_only;
 
 use binder_ndk_sys as sys;
 
@@ -111,15 +115,22 @@
 pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
 pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
 pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
-pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
 #[cfg(not(trusty))]
+pub use persistable_bundle::{PersistableBundle, ValueType};
+pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
+#[cfg(not(any(trusty, android_ndk)))]
 pub use service::{
     add_service, check_interface, check_service, force_lazy_services_persist,
-    get_declared_instances, get_interface, get_service, is_declared, is_handling_transaction,
-    register_lazy_service, wait_for_interface, wait_for_service, LazyServiceGuard,
+    get_declared_instances, is_declared, is_handling_transaction, register_lazy_service,
+    wait_for_interface, wait_for_service, LazyServiceGuard,
 };
-#[cfg(not(trusty))]
+#[cfg(not(any(trusty, android_ndk)))]
+#[allow(deprecated)]
+pub use service::{get_interface, get_service};
+#[cfg(not(any(trusty, android_ndk)))]
 pub use state::{ProcessState, ThreadState};
+#[cfg(not(any(android_vendor, android_vndk, android_ndk, trusty)))]
+pub use system_only::{delegate_accessor, Accessor, AccessorProvider, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
 pub type Result<T> = std::result::Result<T, Status>;
@@ -128,10 +139,12 @@
 /// without AIDL.
 pub mod binder_impl {
     pub use crate::binder::{
-        IBinderInternal, InterfaceClass, Remotable, Stability, ToAsyncInterface, ToSyncInterface,
-        TransactionCode, TransactionFlags, FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY,
-        FLAG_PRIVATE_LOCAL, LAST_CALL_TRANSACTION,
+        IBinderInternal, InterfaceClass, LocalStabilityType, Remotable, Stability, StabilityType,
+        ToAsyncInterface, ToSyncInterface, TransactionCode, TransactionFlags, VintfStabilityType,
+        FIRST_CALL_TRANSACTION, FLAG_ONEWAY, LAST_CALL_TRANSACTION,
     };
+    #[cfg(not(android_ndk))]
+    pub use crate::binder::{FLAG_CLEAR_BUF, FLAG_PRIVATE_LOCAL};
     pub use crate::binder_async::BinderAsyncRuntime;
     pub use crate::error::status_t;
     pub use crate::native::Binder;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index c87cc94..9e1cfd6 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-use crate::binder::{
-    AsNative, Interface, InterfaceClassMethods, Remotable, Stability, TransactionCode,
-};
+#[cfg(not(android_ndk))]
+use crate::binder::Stability;
+use crate::binder::{AsNative, Interface, InterfaceClassMethods, Remotable, TransactionCode};
 use crate::error::{status_result, status_t, Result, StatusCode};
 use crate::parcel::{BorrowedParcel, Serialize};
 use crate::proxy::SpIBinder;
@@ -76,14 +76,32 @@
     /// This moves the `rust_object` into an owned [`Box`] and Binder will
     /// manage its lifetime.
     pub fn new(rust_object: T) -> Binder<T> {
-        Self::new_with_stability(rust_object, Stability::default())
+        #[cfg(not(android_ndk))]
+        {
+            Self::new_with_stability(rust_object, Stability::default())
+        }
+        #[cfg(android_ndk)]
+        {
+            Self::new_unmarked(rust_object)
+        }
     }
 
     /// Create a new Binder remotable object with the given stability
     ///
     /// This moves the `rust_object` into an owned [`Box`] and Binder will
     /// manage its lifetime.
+    #[cfg(not(android_ndk))]
     pub fn new_with_stability(rust_object: T, stability: Stability) -> Binder<T> {
+        let mut binder = Self::new_unmarked(rust_object);
+        binder.mark_stability(stability);
+        binder
+    }
+
+    /// Creates a new Binder remotable object with unset stability
+    ///
+    /// This is internal because normally we want to set the stability explicitly,
+    /// however for the NDK variant we cannot mark the stability.
+    fn new_unmarked(rust_object: T) -> Binder<T> {
         let class = T::get_class();
         let rust_object = Box::into_raw(Box::new(rust_object));
         // Safety: `AIBinder_new` expects a valid class pointer (which we
@@ -93,9 +111,7 @@
         // decremented via `AIBinder_decStrong` when the reference lifetime
         // ends.
         let ibinder = unsafe { sys::AIBinder_new(class.into(), rust_object as *mut c_void) };
-        let mut binder = Binder { ibinder, rust_object };
-        binder.mark_stability(stability);
-        binder
+        Binder { ibinder, rust_object }
     }
 
     /// Set the extension of a binder interface. This allows a downstream
@@ -189,6 +205,7 @@
     }
 
     /// Mark this binder object with the given stability guarantee
+    #[cfg(not(android_ndk))]
     fn mark_stability(&mut self, stability: Stability) {
         match stability {
             Stability::Local => self.mark_local_stability(),
@@ -215,7 +232,7 @@
 
     /// Mark this binder object with local stability, which is vendor if we are
     /// building for android_vendor and system otherwise.
-    #[cfg(not(android_vendor))]
+    #[cfg(not(any(android_vendor, android_ndk)))]
     fn mark_local_stability(&mut self) {
         // Safety: Self always contains a valid `AIBinder` pointer, so we can
         // always call this C API safely.
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index 3bfc425..2d40ced 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -184,7 +184,7 @@
 
 /// Safety: The `BorrowedParcel` constructors guarantee that a `BorrowedParcel`
 /// object will always contain a valid pointer to an `AParcel`.
-unsafe impl<'a> AsNative<sys::AParcel> for BorrowedParcel<'a> {
+unsafe impl AsNative<sys::AParcel> for BorrowedParcel<'_> {
     fn as_native(&self) -> *const sys::AParcel {
         self.ptr.as_ptr()
     }
@@ -195,8 +195,9 @@
 }
 
 // Data serialization methods
-impl<'a> BorrowedParcel<'a> {
+impl BorrowedParcel<'_> {
     /// Data written to parcelable is zero'd before being deleted or reallocated.
+    #[cfg(not(android_ndk))]
     pub fn mark_sensitive(&mut self) {
         // Safety: guaranteed to have a parcel object, and this method never fails
         unsafe { sys::AParcel_markSensitive(self.as_native()) }
@@ -333,7 +334,7 @@
 /// A segment of a writable parcel, used for [`BorrowedParcel::sized_write`].
 pub struct WritableSubParcel<'a>(BorrowedParcel<'a>);
 
-impl<'a> WritableSubParcel<'a> {
+impl WritableSubParcel<'_> {
     /// Write a type that implements [`Serialize`] to the sub-parcel.
     pub fn write<S: Serialize + ?Sized>(&mut self, parcelable: &S) -> Result<()> {
         parcelable.serialize(&mut self.0)
@@ -342,6 +343,7 @@
 
 impl Parcel {
     /// Data written to parcelable is zero'd before being deleted or reallocated.
+    #[cfg(not(android_ndk))]
     pub fn mark_sensitive(&mut self) {
         self.borrowed().mark_sensitive()
     }
@@ -438,7 +440,7 @@
 }
 
 // Data deserialization methods
-impl<'a> BorrowedParcel<'a> {
+impl BorrowedParcel<'_> {
     /// Attempt to read a type that implements [`Deserialize`] from this parcel.
     pub fn read<D: Deserialize>(&self) -> Result<D> {
         D::deserialize(self)
@@ -563,7 +565,7 @@
     end_position: i32,
 }
 
-impl<'a> ReadableSubParcel<'a> {
+impl ReadableSubParcel<'_> {
     /// Read a type that implements [`Deserialize`] from the sub-parcel.
     pub fn read<D: Deserialize>(&self) -> Result<D> {
         D::deserialize(&self.parcel)
@@ -647,7 +649,7 @@
 }
 
 // Internal APIs
-impl<'a> BorrowedParcel<'a> {
+impl BorrowedParcel<'_> {
     pub(crate) fn write_binder(&mut self, binder: Option<&SpIBinder>) -> Result<()> {
         // Safety: `BorrowedParcel` always contains a valid pointer to an
         // `AParcel`. `AsNative` for `Option<SpIBinder`> will either return
@@ -700,7 +702,7 @@
     }
 }
 
-impl<'a> fmt::Debug for BorrowedParcel<'a> {
+impl fmt::Debug for BorrowedParcel<'_> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_struct("BorrowedParcel").finish()
     }
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 33dfe19..7f70396 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -1333,7 +1333,7 @@
         let vec = Vec::<u8>::deserialize(parcel.borrowed_ref()).unwrap();
         assert_eq!(vec, [-128i8 as u8, 127, 42, -117i8 as u8]);
 
-        let u16s = [u16::max_value(), 12_345, 42, 117];
+        let u16s = [u16::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1348,7 +1348,7 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0xffff); // u16::max_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0xffff); // u16::MAX
         assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
@@ -1361,9 +1361,9 @@
 
         let vec = Vec::<u16>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u16::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u16::MAX, 12_345, 42, 117]);
 
-        let i16s = [i16::max_value(), i16::min_value(), 42, -117];
+        let i16s = [i16::MAX, i16::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1378,8 +1378,8 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fff); // i16::max_value()
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x8000); // i16::min_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fff); // i16::MAX
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x8000); // i16::MIN
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 0xff8b); // -117
 
@@ -1391,9 +1391,9 @@
 
         let vec = Vec::<i16>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i16::max_value(), i16::min_value(), 42, -117]);
+        assert_eq!(vec, [i16::MAX, i16::MIN, 42, -117]);
 
-        let u32s = [u32::max_value(), 12_345, 42, 117];
+        let u32s = [u32::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1408,7 +1408,7 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0xffffffff); // u32::max_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0xffffffff); // u32::MAX
         assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
@@ -1421,9 +1421,9 @@
 
         let vec = Vec::<u32>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u32::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u32::MAX, 12_345, 42, 117]);
 
-        let i32s = [i32::max_value(), i32::min_value(), 42, -117];
+        let i32s = [i32::MAX, i32::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1438,8 +1438,8 @@
         }
 
         assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fffffff); // i32::max_value()
-        assert_eq!(parcel.read::<u32>().unwrap(), 0x80000000); // i32::min_value()
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x7fffffff); // i32::MAX
+        assert_eq!(parcel.read::<u32>().unwrap(), 0x80000000); // i32::MIN
         assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
         assert_eq!(parcel.read::<u32>().unwrap(), 0xffffff8b); // -117
 
@@ -1451,9 +1451,9 @@
 
         let vec = Vec::<i32>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i32::max_value(), i32::min_value(), 42, -117]);
+        assert_eq!(vec, [i32::MAX, i32::MIN, 42, -117]);
 
-        let u64s = [u64::max_value(), 12_345, 42, 117];
+        let u64s = [u64::MAX, 12_345, 42, 117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1469,9 +1469,9 @@
 
         let vec = Vec::<u64>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [u64::max_value(), 12_345, 42, 117]);
+        assert_eq!(vec, [u64::MAX, 12_345, 42, 117]);
 
-        let i64s = [i64::max_value(), i64::min_value(), 42, -117];
+        let i64s = [i64::MAX, i64::MIN, 42, -117];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1487,9 +1487,9 @@
 
         let vec = Vec::<i64>::deserialize(parcel.borrowed_ref()).unwrap();
 
-        assert_eq!(vec, [i64::max_value(), i64::min_value(), 42, -117]);
+        assert_eq!(vec, [i64::MAX, i64::MIN, 42, -117]);
 
-        let f32s = [std::f32::NAN, std::f32::INFINITY, 1.23456789, std::f32::EPSILON];
+        let f32s = [f32::NAN, f32::INFINITY, 1.23456789, f32::EPSILON];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
@@ -1509,7 +1509,7 @@
         assert!(vec[0].is_nan());
         assert_eq!(vec[1..], f32s[1..]);
 
-        let f64s = [std::f64::NAN, std::f64::INFINITY, 1.234567890123456789, std::f64::EPSILON];
+        let f64s = [f64::NAN, f64::INFINITY, 1.234567890123456789, f64::EPSILON];
 
         // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
         // made it any shorter since we got the position.
diff --git a/libs/binder/rust/src/parcel/parcelable_holder.rs b/libs/binder/rust/src/parcel/parcelable_holder.rs
index f906113..87b42ab 100644
--- a/libs/binder/rust/src/parcel/parcelable_holder.rs
+++ b/libs/binder/rust/src/parcel/parcelable_holder.rs
@@ -15,6 +15,7 @@
  */
 
 use crate::binder::Stability;
+use crate::binder::StabilityType;
 use crate::error::StatusCode;
 use crate::parcel::{
     BorrowedParcel, Deserialize, Parcel, Parcelable, Serialize, NON_NULL_PARCELABLE_FLAG,
@@ -60,7 +61,7 @@
 /// `Send` nor `Sync`), mainly because it internally contains
 /// a `Parcel` which in turn is not thread-safe.
 #[derive(Debug)]
-pub struct ParcelableHolder {
+pub struct ParcelableHolder<STABILITY: StabilityType> {
     // This is a `Mutex` because of `get_parcelable`
     // which takes `&self` for consistency with C++.
     // We could make `get_parcelable` take a `&mut self`
@@ -68,13 +69,17 @@
     // improvement, but then callers would require a mutable
     // `ParcelableHolder` even for that getter method.
     data: Mutex<ParcelableHolderData>,
-    stability: Stability,
+
+    _stability_phantom: std::marker::PhantomData<STABILITY>,
 }
 
-impl ParcelableHolder {
+impl<STABILITY: StabilityType> ParcelableHolder<STABILITY> {
     /// Construct a new `ParcelableHolder` with the given stability.
-    pub fn new(stability: Stability) -> Self {
-        Self { data: Mutex::new(ParcelableHolderData::Empty), stability }
+    pub fn new() -> Self {
+        Self {
+            data: Mutex::new(ParcelableHolderData::Empty),
+            _stability_phantom: Default::default(),
+        }
     }
 
     /// Reset the contents of this `ParcelableHolder`.
@@ -91,7 +96,7 @@
     where
         T: Any + Parcelable + ParcelableMetadata + std::fmt::Debug + Send + Sync,
     {
-        if self.stability > p.get_stability() {
+        if STABILITY::VALUE > p.get_stability() {
             return Err(StatusCode::BAD_VALUE);
         }
 
@@ -157,30 +162,36 @@
 
     /// Return the stability value of this object.
     pub fn get_stability(&self) -> Stability {
-        self.stability
+        STABILITY::VALUE
     }
 }
 
-impl Clone for ParcelableHolder {
-    fn clone(&self) -> ParcelableHolder {
+impl<STABILITY: StabilityType> Default for ParcelableHolder<STABILITY> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<STABILITY: StabilityType> Clone for ParcelableHolder<STABILITY> {
+    fn clone(&self) -> Self {
         ParcelableHolder {
             data: Mutex::new(self.data.lock().unwrap().clone()),
-            stability: self.stability,
+            _stability_phantom: Default::default(),
         }
     }
 }
 
-impl Serialize for ParcelableHolder {
+impl<STABILITY: StabilityType> Serialize for ParcelableHolder<STABILITY> {
     fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
         parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
         self.write_to_parcel(parcel)
     }
 }
 
-impl Deserialize for ParcelableHolder {
+impl<STABILITY: StabilityType> Deserialize for ParcelableHolder<STABILITY> {
     type UninitType = Self;
     fn uninit() -> Self::UninitType {
-        Self::new(Default::default())
+        Self::new()
     }
     fn from_init(value: Self) -> Self::UninitType {
         value
@@ -191,16 +202,16 @@
         if status == NULL_PARCELABLE_FLAG {
             Err(StatusCode::UNEXPECTED_NULL)
         } else {
-            let mut parcelable = ParcelableHolder::new(Default::default());
+            let mut parcelable = Self::new();
             parcelable.read_from_parcel(parcel)?;
             Ok(parcelable)
         }
     }
 }
 
-impl Parcelable for ParcelableHolder {
+impl<STABILITY: StabilityType> Parcelable for ParcelableHolder<STABILITY> {
     fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
-        parcel.write(&self.stability)?;
+        parcel.write(&STABILITY::VALUE)?;
 
         let mut data = self.data.lock().unwrap();
         match *data {
@@ -236,7 +247,7 @@
     }
 
     fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<(), StatusCode> {
-        if self.stability != parcel.read()? {
+        if self.get_stability() != parcel.read()? {
             return Err(StatusCode::BAD_VALUE);
         }
 
diff --git a/libs/binder/rust/src/persistable_bundle.rs b/libs/binder/rust/src/persistable_bundle.rs
new file mode 100644
index 0000000..8639c0d
--- /dev/null
+++ b/libs/binder/rust/src/persistable_bundle.rs
@@ -0,0 +1,1076 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+use crate::{
+    binder::AsNative,
+    error::{status_result, StatusCode},
+    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+    parcel::{BorrowedParcel, UnstructuredParcelable},
+};
+use binder_ndk_sys::{
+    APersistableBundle, APersistableBundle_delete, APersistableBundle_dup,
+    APersistableBundle_erase, APersistableBundle_getBoolean, APersistableBundle_getBooleanKeys,
+    APersistableBundle_getBooleanVector, APersistableBundle_getBooleanVectorKeys,
+    APersistableBundle_getDouble, APersistableBundle_getDoubleKeys,
+    APersistableBundle_getDoubleVector, APersistableBundle_getDoubleVectorKeys,
+    APersistableBundle_getInt, APersistableBundle_getIntKeys, APersistableBundle_getIntVector,
+    APersistableBundle_getIntVectorKeys, APersistableBundle_getLong,
+    APersistableBundle_getLongKeys, APersistableBundle_getLongVector,
+    APersistableBundle_getLongVectorKeys, APersistableBundle_getPersistableBundle,
+    APersistableBundle_getPersistableBundleKeys, APersistableBundle_getString,
+    APersistableBundle_getStringKeys, APersistableBundle_getStringVector,
+    APersistableBundle_getStringVectorKeys, APersistableBundle_isEqual, APersistableBundle_new,
+    APersistableBundle_putBoolean, APersistableBundle_putBooleanVector,
+    APersistableBundle_putDouble, APersistableBundle_putDoubleVector, APersistableBundle_putInt,
+    APersistableBundle_putIntVector, APersistableBundle_putLong, APersistableBundle_putLongVector,
+    APersistableBundle_putPersistableBundle, APersistableBundle_putString,
+    APersistableBundle_putStringVector, APersistableBundle_readFromParcel, APersistableBundle_size,
+    APersistableBundle_writeToParcel, APERSISTABLEBUNDLE_ALLOCATOR_FAILED,
+    APERSISTABLEBUNDLE_KEY_NOT_FOUND,
+};
+use std::ffi::{c_char, c_void, CStr, CString, NulError};
+use std::ptr::{null_mut, slice_from_raw_parts_mut, NonNull};
+use zerocopy::FromZeros;
+
+/// A mapping from string keys to values of various types.
+#[derive(Debug)]
+pub struct PersistableBundle(NonNull<APersistableBundle>);
+
+impl PersistableBundle {
+    /// Creates a new `PersistableBundle`.
+    pub fn new() -> Self {
+        // SAFETY: APersistableBundle_new doesn't actually have any safety requirements.
+        let bundle = unsafe { APersistableBundle_new() };
+        Self(NonNull::new(bundle).expect("Allocated APersistableBundle was null"))
+    }
+
+    /// Returns the number of mappings in the bundle.
+    pub fn size(&self) -> usize {
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`.
+        unsafe { APersistableBundle_size(self.0.as_ptr()) }
+            .try_into()
+            .expect("APersistableBundle_size returned a negative size")
+    }
+
+    /// Removes any entry with the given key.
+    ///
+    /// Returns an error if the given key contains a NUL character, otherwise returns whether there
+    /// was any entry to remove.
+    pub fn remove(&mut self, key: &str) -> Result<bool, NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call.
+        Ok(unsafe { APersistableBundle_erase(self.0.as_ptr(), key.as_ptr()) != 0 })
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_bool(&mut self, key: &str, value: bool) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call.
+        unsafe {
+            APersistableBundle_putBoolean(self.0.as_ptr(), key.as_ptr(), value);
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_int(&mut self, key: &str, value: i32) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call.
+        unsafe {
+            APersistableBundle_putInt(self.0.as_ptr(), key.as_ptr(), value);
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_long(&mut self, key: &str, value: i64) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call.
+        unsafe {
+            APersistableBundle_putLong(self.0.as_ptr(), key.as_ptr(), value);
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_double(&mut self, key: &str, value: f64) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call.
+        unsafe {
+            APersistableBundle_putDouble(self.0.as_ptr(), key.as_ptr(), value);
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key or value contains a NUL character.
+    pub fn insert_string(&mut self, key: &str, value: &str) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        let value = CString::new(value)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `CStr::as_ptr` is guaranteed
+        // to be valid for the duration of this call.
+        unsafe {
+            APersistableBundle_putString(self.0.as_ptr(), key.as_ptr(), value.as_ptr());
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_bool_vec(&mut self, key: &str, value: &[bool]) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call, and likewise the pointer returned by
+        // `value.as_ptr()` is guaranteed to be valid for at least `value.len()` values for the
+        // duration of the call.
+        unsafe {
+            APersistableBundle_putBooleanVector(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                value.as_ptr(),
+                value.len().try_into().unwrap(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_int_vec(&mut self, key: &str, value: &[i32]) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call, and likewise the pointer returned by
+        // `value.as_ptr()` is guaranteed to be valid for at least `value.len()` values for the
+        // duration of the call.
+        unsafe {
+            APersistableBundle_putIntVector(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                value.as_ptr(),
+                value.len().try_into().unwrap(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_long_vec(&mut self, key: &str, value: &[i64]) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call, and likewise the pointer returned by
+        // `value.as_ptr()` is guaranteed to be valid for at least `value.len()` values for the
+        // duration of the call.
+        unsafe {
+            APersistableBundle_putLongVector(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                value.as_ptr(),
+                value.len().try_into().unwrap(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_double_vec(&mut self, key: &str, value: &[f64]) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call, and likewise the pointer returned by
+        // `value.as_ptr()` is guaranteed to be valid for at least `value.len()` values for the
+        // duration of the call.
+        unsafe {
+            APersistableBundle_putDoubleVector(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                value.as_ptr(),
+                value.len().try_into().unwrap(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_string_vec<'a, T: ToString + 'a>(
+        &mut self,
+        key: &str,
+        value: impl IntoIterator<Item = &'a T>,
+    ) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // We need to collect the new `CString`s into something first so that they live long enough
+        // for their pointers to be valid for the `APersistableBundle_putStringVector` call below.
+        let c_strings = value
+            .into_iter()
+            .map(|s| CString::new(s.to_string()))
+            .collect::<Result<Vec<_>, NulError>>()?;
+        let char_pointers = c_strings.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call, and likewise the pointer returned by
+        // `value.as_ptr()` is guaranteed to be valid for at least `value.len()` values for the
+        // duration of the call.
+        unsafe {
+            APersistableBundle_putStringVector(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                char_pointers.as_ptr(),
+                char_pointers.len().try_into().unwrap(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Inserts a key-value pair into the bundle.
+    ///
+    /// If the key is already present then its value will be overwritten by the given value.
+    ///
+    /// Returns an error if the key contains a NUL character.
+    pub fn insert_persistable_bundle(
+        &mut self,
+        key: &str,
+        value: &PersistableBundle,
+    ) -> Result<(), NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointers are guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`s. The pointer returned by `CStr::as_ptr` is
+        // guaranteed to be valid for the duration of this call, and
+        // `APersistableBundle_putPersistableBundle` does a deep copy so that is all that is
+        // required.
+        unsafe {
+            APersistableBundle_putPersistableBundle(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                value.0.as_ptr(),
+            );
+        }
+        Ok(())
+    }
+
+    /// Gets the boolean value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_bool(&self, key: &str) -> Result<Option<bool>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = false;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call. The value pointer must be valid because it
+        // comes from a reference.
+        if unsafe { APersistableBundle_getBoolean(self.0.as_ptr(), key.as_ptr(), &mut value) } {
+            Ok(Some(value))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Gets the i32 value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_int(&self, key: &str) -> Result<Option<i32>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = 0;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call. The value pointer must be valid because it
+        // comes from a reference.
+        if unsafe { APersistableBundle_getInt(self.0.as_ptr(), key.as_ptr(), &mut value) } {
+            Ok(Some(value))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Gets the i64 value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_long(&self, key: &str) -> Result<Option<i64>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = 0;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call. The value pointer must be valid because it
+        // comes from a reference.
+        if unsafe { APersistableBundle_getLong(self.0.as_ptr(), key.as_ptr(), &mut value) } {
+            Ok(Some(value))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Gets the f64 value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_double(&self, key: &str) -> Result<Option<f64>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = 0.0;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the duration of this call. The value pointer must be valid because it
+        // comes from a reference.
+        if unsafe { APersistableBundle_getDouble(self.0.as_ptr(), key.as_ptr(), &mut value) } {
+            Ok(Some(value))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Gets the string value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_string(&self, key: &str) -> Result<Option<String>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = null_mut();
+        let mut allocated_size: usize = 0;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the lifetime of `key`. The value pointer must be valid because it comes
+        // from a reference.
+        let value_size_bytes = unsafe {
+            APersistableBundle_getString(
+                self.0.as_ptr(),
+                key.as_ptr(),
+                &mut value,
+                Some(string_allocator),
+                (&raw mut allocated_size).cast(),
+            )
+        };
+        match value_size_bytes {
+            APERSISTABLEBUNDLE_KEY_NOT_FOUND => Ok(None),
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_getString failed to allocate string");
+            }
+            _ => {
+                let raw_slice = slice_from_raw_parts_mut(value.cast(), allocated_size);
+                // SAFETY: The pointer was returned from string_allocator, which used
+                // `Box::into_raw`, and we've got the appropriate size back from allocated_size.
+                let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                assert_eq!(
+                    allocated_size,
+                    usize::try_from(value_size_bytes)
+                        .expect("APersistableBundle_getString returned negative value size")
+                        + 1
+                );
+                let c_string = CString::from_vec_with_nul(boxed_slice.into())
+                    .expect("APersistableBundle_getString returned string missing NUL byte");
+                let string = c_string
+                    .into_string()
+                    .expect("APersistableBundle_getString returned invalid UTF-8");
+                Ok(Some(string))
+            }
+        }
+    }
+
+    /// Gets the vector of `T` associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    ///
+    /// `get_func` should be one of the `APersistableBundle_get*Vector` functions from
+    /// `binder_ndk_sys`.
+    ///
+    /// # Safety
+    ///
+    /// `get_func` must only require that the pointers it takes are valid for the duration of the
+    /// call. It must allow a null pointer for the buffer, and must return the size in bytes of
+    /// buffer it requires. If it is given a non-null buffer pointer it must write that number of
+    /// bytes to the buffer, which must be a whole number of valid `T` values.
+    unsafe fn get_vec<T: Clone>(
+        &self,
+        key: &str,
+        default: T,
+        get_func: unsafe extern "C" fn(
+            *const APersistableBundle,
+            *const c_char,
+            *mut T,
+            i32,
+        ) -> i32,
+    ) -> Result<Option<Vec<T>>, NulError> {
+        let key = CString::new(key)?;
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the lifetime of `key`. A null pointer is allowed for the buffer.
+        match unsafe { get_func(self.0.as_ptr(), key.as_ptr(), null_mut(), 0) } {
+            APERSISTABLEBUNDLE_KEY_NOT_FOUND => Ok(None),
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_getStringVector failed to allocate string");
+            }
+            required_buffer_size => {
+                let mut value = vec![
+                    default;
+                    usize::try_from(required_buffer_size).expect(
+                        "APersistableBundle_get*Vector returned invalid size"
+                    ) / size_of::<T>()
+                ];
+                // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for
+                // the lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()`
+                // is guaranteed to be valid for the lifetime of `key`. The value buffer pointer is
+                // valid as it comes from the Vec we just allocated.
+                match unsafe {
+                    get_func(
+                        self.0.as_ptr(),
+                        key.as_ptr(),
+                        value.as_mut_ptr(),
+                        (value.len() * size_of::<T>()).try_into().unwrap(),
+                    )
+                } {
+                    APERSISTABLEBUNDLE_KEY_NOT_FOUND => {
+                        panic!("APersistableBundle_get*Vector failed to find key after first finding it");
+                    }
+                    APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                        panic!("APersistableBundle_getStringVector failed to allocate string");
+                    }
+                    _ => Ok(Some(value)),
+                }
+            }
+        }
+    }
+
+    /// Gets the boolean vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_bool_vec(&self, key: &str) -> Result<Option<Vec<bool>>, NulError> {
+        // SAFETY: APersistableBundle_getBooleanVector fulfils all the safety requirements of
+        // `get_vec`.
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getBooleanVector) }
+    }
+
+    /// Gets the i32 vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_int_vec(&self, key: &str) -> Result<Option<Vec<i32>>, NulError> {
+        // SAFETY: APersistableBundle_getIntVector fulfils all the safety requirements of
+        // `get_vec`.
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getIntVector) }
+    }
+
+    /// Gets the i64 vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_long_vec(&self, key: &str) -> Result<Option<Vec<i64>>, NulError> {
+        // SAFETY: APersistableBundle_getLongVector fulfils all the safety requirements of
+        // `get_vec`.
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getLongVector) }
+    }
+
+    /// Gets the f64 vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_double_vec(&self, key: &str) -> Result<Option<Vec<f64>>, NulError> {
+        // SAFETY: APersistableBundle_getDoubleVector fulfils all the safety requirements of
+        // `get_vec`.
+        unsafe { self.get_vec(key, Default::default(), APersistableBundle_getDoubleVector) }
+    }
+
+    /// Gets the string vector value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_string_vec(&self, key: &str) -> Result<Option<Vec<String>>, NulError> {
+        if let Some(value) =
+            // SAFETY: `get_string_vector_with_allocator` fulfils all the safety requirements of
+            // `get_vec`.
+            unsafe { self.get_vec(key, null_mut(), get_string_vector_with_allocator) }?
+        {
+            Ok(Some(
+                value
+                    .into_iter()
+                    .map(|s| {
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and `APersistableBundle_getStringVector` should have
+                        // written valid bytes to it including a NUL terminator in the last
+                        // position.
+                        let string_length = unsafe { CStr::from_ptr(s) }.count_bytes();
+                        let raw_slice = slice_from_raw_parts_mut(s.cast(), string_length + 1);
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and we've got the appropriate size back by checking the
+                        // length of the string.
+                        let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                        let c_string = CString::from_vec_with_nul(boxed_slice.into()).expect(
+                            "APersistableBundle_getStringVector returned string missing NUL byte",
+                        );
+                        c_string
+                            .into_string()
+                            .expect("APersistableBundle_getStringVector returned invalid UTF-8")
+                    })
+                    .collect(),
+            ))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Gets the `PersistableBundle` value associated with the given key.
+    ///
+    /// Returns an error if the key contains a NUL character, or `Ok(None)` if the key doesn't exist
+    /// in the bundle.
+    pub fn get_persistable_bundle(&self, key: &str) -> Result<Option<Self>, NulError> {
+        let key = CString::new(key)?;
+        let mut value = null_mut();
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. The pointer returned by `key.as_ptr()` is guaranteed
+        // to be valid for the lifetime of `key`. The value pointer must be valid because it comes
+        // from a reference.
+        if unsafe {
+            APersistableBundle_getPersistableBundle(self.0.as_ptr(), key.as_ptr(), &mut value)
+        } {
+            Ok(Some(Self(NonNull::new(value).expect(
+                "APersistableBundle_getPersistableBundle returned true but didn't set outBundle",
+            ))))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Calls the appropriate `APersistableBundle_get*Keys` function for the given `value_type`,
+    /// with our `string_allocator` and a null context pointer.
+    ///
+    /// # Safety
+    ///
+    /// `out_keys` must either be null or point to a buffer of at least `buffer_size_bytes` bytes,
+    /// properly aligned for `T`, and not otherwise accessed for the duration of the call.
+    unsafe fn get_keys_raw(
+        &self,
+        value_type: ValueType,
+        out_keys: *mut *mut c_char,
+        buffer_size_bytes: i32,
+    ) -> i32 {
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. Our caller guarantees an appropriate value for
+        // `out_keys` and `buffer_size_bytes`.
+        unsafe {
+            match value_type {
+                ValueType::Boolean => APersistableBundle_getBooleanKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Integer => APersistableBundle_getIntKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Long => APersistableBundle_getLongKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::Double => APersistableBundle_getDoubleKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::String => APersistableBundle_getStringKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::BooleanVector => APersistableBundle_getBooleanVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::IntegerVector => APersistableBundle_getIntVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::LongVector => APersistableBundle_getLongVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::DoubleVector => APersistableBundle_getDoubleVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::StringVector => APersistableBundle_getStringVectorKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+                ValueType::PersistableBundle => APersistableBundle_getPersistableBundleKeys(
+                    self.0.as_ptr(),
+                    out_keys,
+                    buffer_size_bytes,
+                    Some(string_allocator),
+                    null_mut(),
+                ),
+            }
+        }
+    }
+
+    /// Gets all the keys associated with values of the given type.
+    pub fn keys_for_type(&self, value_type: ValueType) -> Vec<String> {
+        // SAFETY: A null pointer is allowed for the buffer.
+        match unsafe { self.get_keys_raw(value_type, null_mut(), 0) } {
+            APERSISTABLEBUNDLE_ALLOCATOR_FAILED => {
+                panic!("APersistableBundle_get*Keys failed to allocate string");
+            }
+            required_buffer_size => {
+                let required_buffer_size_usize = usize::try_from(required_buffer_size)
+                    .expect("APersistableBundle_get*Keys returned invalid size");
+                assert_eq!(required_buffer_size_usize % size_of::<*mut c_char>(), 0);
+                let mut keys =
+                    vec![null_mut(); required_buffer_size_usize / size_of::<*mut c_char>()];
+                // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for
+                // the lifetime of the `PersistableBundle`. The keys buffer pointer is valid as it
+                // comes from the Vec we just allocated.
+                if unsafe { self.get_keys_raw(value_type, keys.as_mut_ptr(), required_buffer_size) }
+                    == APERSISTABLEBUNDLE_ALLOCATOR_FAILED
+                {
+                    panic!("APersistableBundle_get*Keys failed to allocate string");
+                }
+                keys.into_iter()
+                    .map(|key| {
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and `APersistableBundle_getStringVector` should have
+                        // written valid bytes to it including a NUL terminator in the last
+                        // position.
+                        let string_length = unsafe { CStr::from_ptr(key) }.count_bytes();
+                        let raw_slice = slice_from_raw_parts_mut(key.cast(), string_length + 1);
+                        // SAFETY: The pointer was returned from `string_allocator`, which used
+                        // `Box::into_raw`, and we've got the appropriate size back by checking the
+                        // length of the string.
+                        let boxed_slice: Box<[u8]> = unsafe { Box::from_raw(raw_slice) };
+                        let c_string = CString::from_vec_with_nul(boxed_slice.into())
+                            .expect("APersistableBundle_get*Keys returned string missing NUL byte");
+                        c_string
+                            .into_string()
+                            .expect("APersistableBundle_get*Keys returned invalid UTF-8")
+                    })
+                    .collect()
+            }
+        }
+    }
+
+    /// Returns an iterator over all keys in the bundle, along with the type of their associated
+    /// value.
+    pub fn keys(&self) -> impl Iterator<Item = (String, ValueType)> + use<'_> {
+        [
+            ValueType::Boolean,
+            ValueType::Integer,
+            ValueType::Long,
+            ValueType::Double,
+            ValueType::String,
+            ValueType::BooleanVector,
+            ValueType::IntegerVector,
+            ValueType::LongVector,
+            ValueType::DoubleVector,
+            ValueType::StringVector,
+            ValueType::PersistableBundle,
+        ]
+        .iter()
+        .flat_map(|value_type| {
+            self.keys_for_type(*value_type).into_iter().map(|key| (key, *value_type))
+        })
+    }
+}
+
+/// Wrapper around `APersistableBundle_getStringVector` to pass `string_allocator` and a null
+/// context pointer.
+///
+/// # Safety
+///
+/// * `bundle` must point to a valid `APersistableBundle` which is not modified for the duration of
+///   the call.
+/// * `key` must point to a valid NUL-terminated C string.
+/// * `buffer` must either be null or point to a buffer of at least `buffer_size_bytes` bytes,
+///   properly aligned for `T`, and not otherwise accessed for the duration of the call.
+unsafe extern "C" fn get_string_vector_with_allocator(
+    bundle: *const APersistableBundle,
+    key: *const c_char,
+    buffer: *mut *mut c_char,
+    buffer_size_bytes: i32,
+) -> i32 {
+    // SAFETY: The safety requirements are all guaranteed by our caller according to the safety
+    // documentation above.
+    unsafe {
+        APersistableBundle_getStringVector(
+            bundle,
+            key,
+            buffer,
+            buffer_size_bytes,
+            Some(string_allocator),
+            null_mut(),
+        )
+    }
+}
+
+// SAFETY: The underlying *APersistableBundle can be moved between threads.
+unsafe impl Send for PersistableBundle {}
+
+// SAFETY: The underlying *APersistableBundle can be read from multiple threads, and we require
+// `&mut PersistableBundle` for any operations which mutate it.
+unsafe impl Sync for PersistableBundle {}
+
+impl Default for PersistableBundle {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Drop for PersistableBundle {
+    fn drop(&mut self) {
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of this `PersistableBundle`.
+        unsafe { APersistableBundle_delete(self.0.as_ptr()) };
+    }
+}
+
+impl Clone for PersistableBundle {
+    fn clone(&self) -> Self {
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`.
+        let duplicate = unsafe { APersistableBundle_dup(self.0.as_ptr()) };
+        Self(NonNull::new(duplicate).expect("Duplicated APersistableBundle was null"))
+    }
+}
+
+impl PartialEq for PersistableBundle {
+    fn eq(&self, other: &Self) -> bool {
+        // SAFETY: The wrapped `APersistableBundle` pointers are guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`s.
+        unsafe { APersistableBundle_isEqual(self.0.as_ptr(), other.0.as_ptr()) }
+    }
+}
+
+impl UnstructuredParcelable for PersistableBundle {
+    fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+        let status =
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. `parcel.as_native_mut()` always returns a valid
+        // parcel pointer.
+            unsafe { APersistableBundle_writeToParcel(self.0.as_ptr(), parcel.as_native_mut()) };
+        status_result(status)
+    }
+
+    fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+        let mut bundle = null_mut();
+
+        // SAFETY: The wrapped `APersistableBundle` pointer is guaranteed to be valid for the
+        // lifetime of the `PersistableBundle`. `parcel.as_native()` always returns a valid parcel
+        // pointer.
+        let status = unsafe { APersistableBundle_readFromParcel(parcel.as_native(), &mut bundle) };
+        status_result(status)?;
+
+        Ok(Self(NonNull::new(bundle).expect(
+            "APersistableBundle_readFromParcel returned success but didn't allocate bundle",
+        )))
+    }
+}
+
+/// Allocates a boxed slice of the given size in bytes, returns a pointer to it and writes its size
+/// to `*context`.
+///
+/// # Safety
+///
+/// `context` must either be null or point to a `usize` to which we can write.
+unsafe extern "C" fn string_allocator(size: i32, context: *mut c_void) -> *mut c_char {
+    let Ok(size) = size.try_into() else {
+        return null_mut();
+    };
+    let Ok(boxed_slice) = <[c_char]>::new_box_zeroed_with_elems(size) else {
+        return null_mut();
+    };
+    if !context.is_null() {
+        // SAFETY: The caller promised that `context` is either null or points to a `usize` to which
+        // we can write, and we just checked that it's not null.
+        unsafe {
+            *context.cast::<usize>() = size;
+        }
+    }
+    Box::into_raw(boxed_slice).cast()
+}
+
+impl_deserialize_for_unstructured_parcelable!(PersistableBundle);
+impl_serialize_for_unstructured_parcelable!(PersistableBundle);
+
+/// The types which may be stored as values in a [`PersistableBundle`].
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum ValueType {
+    /// A `bool`.
+    Boolean,
+    /// An `i32`.
+    Integer,
+    /// An `i64`.
+    Long,
+    /// An `f64`.
+    Double,
+    /// A string.
+    String,
+    /// A vector of `bool`s.
+    BooleanVector,
+    /// A vector of `i32`s.
+    IntegerVector,
+    /// A vector of `i64`s.
+    LongVector,
+    /// A vector of `f64`s.
+    DoubleVector,
+    /// A vector of strings.
+    StringVector,
+    /// A nested `PersistableBundle`.
+    PersistableBundle,
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn create_delete() {
+        let bundle = PersistableBundle::new();
+        drop(bundle);
+    }
+
+    #[test]
+    fn duplicate_equal() {
+        let bundle = PersistableBundle::new();
+        let duplicate = bundle.clone();
+        assert_eq!(bundle, duplicate);
+    }
+
+    #[test]
+    fn get_empty() {
+        let bundle = PersistableBundle::new();
+        assert_eq!(bundle.get_bool("foo"), Ok(None));
+        assert_eq!(bundle.get_int("foo"), Ok(None));
+        assert_eq!(bundle.get_long("foo"), Ok(None));
+        assert_eq!(bundle.get_double("foo"), Ok(None));
+        assert_eq!(bundle.get_bool_vec("foo"), Ok(None));
+        assert_eq!(bundle.get_int_vec("foo"), Ok(None));
+        assert_eq!(bundle.get_long_vec("foo"), Ok(None));
+        assert_eq!(bundle.get_double_vec("foo"), Ok(None));
+        assert_eq!(bundle.get_string("foo"), Ok(None));
+    }
+
+    #[test]
+    fn remove_empty() {
+        let mut bundle = PersistableBundle::new();
+        assert_eq!(bundle.remove("foo"), Ok(false));
+    }
+
+    #[test]
+    fn insert_get_primitives() {
+        let mut bundle = PersistableBundle::new();
+
+        assert_eq!(bundle.insert_bool("bool", true), Ok(()));
+        assert_eq!(bundle.insert_int("int", 42), Ok(()));
+        assert_eq!(bundle.insert_long("long", 66), Ok(()));
+        assert_eq!(bundle.insert_double("double", 123.4), Ok(()));
+
+        assert_eq!(bundle.get_bool("bool"), Ok(Some(true)));
+        assert_eq!(bundle.get_int("int"), Ok(Some(42)));
+        assert_eq!(bundle.get_long("long"), Ok(Some(66)));
+        assert_eq!(bundle.get_double("double"), Ok(Some(123.4)));
+        assert_eq!(bundle.size(), 4);
+
+        // Getting the wrong type should return nothing.
+        assert_eq!(bundle.get_int("bool"), Ok(None));
+        assert_eq!(bundle.get_long("bool"), Ok(None));
+        assert_eq!(bundle.get_double("bool"), Ok(None));
+        assert_eq!(bundle.get_bool("int"), Ok(None));
+        assert_eq!(bundle.get_long("int"), Ok(None));
+        assert_eq!(bundle.get_double("int"), Ok(None));
+        assert_eq!(bundle.get_bool("long"), Ok(None));
+        assert_eq!(bundle.get_int("long"), Ok(None));
+        assert_eq!(bundle.get_double("long"), Ok(None));
+        assert_eq!(bundle.get_bool("double"), Ok(None));
+        assert_eq!(bundle.get_int("double"), Ok(None));
+        assert_eq!(bundle.get_long("double"), Ok(None));
+
+        // If they are removed they should no longer be present.
+        assert_eq!(bundle.remove("bool"), Ok(true));
+        assert_eq!(bundle.remove("int"), Ok(true));
+        assert_eq!(bundle.remove("long"), Ok(true));
+        assert_eq!(bundle.remove("double"), Ok(true));
+        assert_eq!(bundle.get_bool("bool"), Ok(None));
+        assert_eq!(bundle.get_int("int"), Ok(None));
+        assert_eq!(bundle.get_long("long"), Ok(None));
+        assert_eq!(bundle.get_double("double"), Ok(None));
+        assert_eq!(bundle.size(), 0);
+    }
+
+    #[test]
+    fn insert_get_string() {
+        let mut bundle = PersistableBundle::new();
+
+        assert_eq!(bundle.insert_string("string", "foo"), Ok(()));
+        assert_eq!(bundle.insert_string("empty", ""), Ok(()));
+        assert_eq!(bundle.size(), 2);
+
+        assert_eq!(bundle.get_string("string"), Ok(Some("foo".to_string())));
+        assert_eq!(bundle.get_string("empty"), Ok(Some("".to_string())));
+    }
+
+    #[test]
+    fn insert_get_vec() {
+        let mut bundle = PersistableBundle::new();
+
+        assert_eq!(bundle.insert_bool_vec("bool", &[]), Ok(()));
+        assert_eq!(bundle.insert_int_vec("int", &[42]), Ok(()));
+        assert_eq!(bundle.insert_long_vec("long", &[66, 67, 68]), Ok(()));
+        assert_eq!(bundle.insert_double_vec("double", &[123.4]), Ok(()));
+        assert_eq!(bundle.insert_string_vec("string", &["foo", "bar", "baz"]), Ok(()));
+        assert_eq!(
+            bundle.insert_string_vec(
+                "string",
+                &[&"foo".to_string(), &"bar".to_string(), &"baz".to_string()]
+            ),
+            Ok(())
+        );
+        assert_eq!(
+            bundle.insert_string_vec(
+                "string",
+                &["foo".to_string(), "bar".to_string(), "baz".to_string()]
+            ),
+            Ok(())
+        );
+
+        assert_eq!(bundle.size(), 5);
+
+        assert_eq!(bundle.get_bool_vec("bool"), Ok(Some(vec![])));
+        assert_eq!(bundle.get_int_vec("int"), Ok(Some(vec![42])));
+        assert_eq!(bundle.get_long_vec("long"), Ok(Some(vec![66, 67, 68])));
+        assert_eq!(bundle.get_double_vec("double"), Ok(Some(vec![123.4])));
+        assert_eq!(
+            bundle.get_string_vec("string"),
+            Ok(Some(vec!["foo".to_string(), "bar".to_string(), "baz".to_string()]))
+        );
+    }
+
+    #[test]
+    fn insert_get_bundle() {
+        let mut bundle = PersistableBundle::new();
+
+        let mut sub_bundle = PersistableBundle::new();
+        assert_eq!(sub_bundle.insert_int("int", 42), Ok(()));
+        assert_eq!(sub_bundle.size(), 1);
+        assert_eq!(bundle.insert_persistable_bundle("bundle", &sub_bundle), Ok(()));
+
+        assert_eq!(bundle.get_persistable_bundle("bundle"), Ok(Some(sub_bundle)));
+    }
+
+    #[test]
+    fn get_keys() {
+        let mut bundle = PersistableBundle::new();
+
+        assert_eq!(bundle.keys_for_type(ValueType::Boolean), Vec::<String>::new());
+        assert_eq!(bundle.keys_for_type(ValueType::Integer), Vec::<String>::new());
+        assert_eq!(bundle.keys_for_type(ValueType::StringVector), Vec::<String>::new());
+
+        assert_eq!(bundle.insert_bool("bool1", false), Ok(()));
+        assert_eq!(bundle.insert_bool("bool2", true), Ok(()));
+        assert_eq!(bundle.insert_int("int", 42), Ok(()));
+
+        assert_eq!(
+            bundle.keys_for_type(ValueType::Boolean),
+            vec!["bool1".to_string(), "bool2".to_string()]
+        );
+        assert_eq!(bundle.keys_for_type(ValueType::Integer), vec!["int".to_string()]);
+        assert_eq!(bundle.keys_for_type(ValueType::StringVector), Vec::<String>::new());
+
+        assert_eq!(
+            bundle.keys().collect::<Vec<_>>(),
+            vec![
+                ("bool1".to_string(), ValueType::Boolean),
+                ("bool2".to_string(), ValueType::Boolean),
+                ("int".to_string(), ValueType::Integer),
+            ]
+        );
+    }
+}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 340014a..593d12c 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -195,7 +195,7 @@
 
 impl PartialEq for SpIBinder {
     fn eq(&self, other: &Self) -> bool {
-        ptr::eq(self.0.as_ptr(), other.0.as_ptr())
+        self.cmp(other) == Ordering::Equal
     }
 }
 
@@ -298,7 +298,7 @@
         unsafe { sys::AIBinder_isAlive(self.as_native()) }
     }
 
-    #[cfg(not(android_vndk))]
+    #[cfg(not(any(android_vndk, android_ndk)))]
     fn set_requesting_sid(&mut self, enable: bool) {
         // Safety: `SpIBinder` guarantees that `self` always contains a valid
         // pointer to an `AIBinder`.
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
index 29dd8e1..f4fdcf5 100644
--- a/libs/binder/rust/src/service.rs
+++ b/libs/binder/rust/src/service.rs
@@ -176,6 +176,7 @@
 /// seconds if it doesn't yet exist.
 #[deprecated = "this polls 5s, use wait_for_interface or check_interface"]
 pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+    #[allow(deprecated)]
     interface_cast(get_service(name))
 }
 
diff --git a/libs/binder/rust/src/state.rs b/libs/binder/rust/src/state.rs
index 8a06274..145ae65 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -28,8 +28,9 @@
     /// `num_threads` additional threads as specified by
     /// [`set_thread_pool_max_thread_count`](Self::set_thread_pool_max_thread_count).
     ///
-    /// This should be done before creating any Binder client or server. If
-    /// neither this nor [`join_thread_pool`](Self::join_thread_pool) are
+    /// If this is called, it must be done before creating any Binder client or server.
+    ///
+    /// If neither this nor [`join_thread_pool`](Self::join_thread_pool) are
     /// called, then some things (such as callbacks and
     /// [`IBinder::link_to_death`](crate::IBinder::link_to_death)) will silently
     /// not work: the callbacks will be queued but never called as there is no
diff --git a/libs/binder/rust/src/system_only.rs b/libs/binder/rust/src/system_only.rs
new file mode 100644
index 0000000..50aa336
--- /dev/null
+++ b/libs/binder/rust/src/system_only.rs
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+use crate::binder::AsNative;
+use crate::error::{status_result, Result};
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+
+use libc::{sockaddr, sockaddr_un, sockaddr_vm, socklen_t};
+use std::boxed::Box;
+use std::{mem, ptr};
+
+/// Rust wrapper around ABinderRpc_Accessor objects for RPC binder service management.
+///
+/// Dropping the `Accessor` will drop the underlying object and the binder it owns.
+#[derive(Debug)]
+pub struct Accessor {
+    accessor: *mut sys::ABinderRpc_Accessor,
+}
+
+/// Socket connection info required for libbinder to connect to a service.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ConnectionInfo {
+    /// For vsock connection
+    Vsock(sockaddr_vm),
+    /// For unix domain socket connection
+    Unix(sockaddr_un),
+}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As
+/// `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Send for Accessor {}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Sync for Accessor {}
+
+impl Accessor {
+    /// Create a new accessor that will call the given callback when its
+    /// connection info is required.
+    /// The callback object and all objects it captures are owned by the Accessor
+    /// and will be deleted some time after the Accessor is Dropped. If the callback
+    /// is being called when the Accessor is Dropped, the callback will not be deleted
+    /// immediately.
+    pub fn new<F>(instance: &str, callback: F) -> Accessor
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        let callback: *mut c_void = Box::into_raw(Box::new(callback)) as *mut c_void;
+        let inst = CString::new(instance).unwrap();
+
+        // Safety: The function pointer is a valid connection_info callback.
+        // This call returns an owned `ABinderRpc_Accessor` pointer which
+        // must be destroyed via `ABinderRpc_Accessor_delete` when no longer
+        // needed.
+        // When the underlying ABinderRpc_Accessor is deleted, it will call
+        // the cookie_decr_refcount callback to release its strong ref.
+        let accessor = unsafe {
+            sys::ABinderRpc_Accessor_new(
+                inst.as_ptr(),
+                Some(Self::connection_info::<F>),
+                callback,
+                Some(Self::cookie_decr_refcount::<F>),
+            )
+        };
+
+        Accessor { accessor }
+    }
+
+    /// Creates a new Accessor instance based on an existing Accessor's binder.
+    /// This is useful when the Accessor instance is hosted in another process
+    /// that has the permissions to create the socket connection FD.
+    ///
+    /// The `instance` argument must match the instance that the original Accessor
+    /// is responsible for.
+    /// `instance` must not contain null bytes and is used to create a CString to
+    /// pass through FFI.
+    /// The `binder` argument must be a valid binder from an Accessor
+    pub fn from_binder(instance: &str, binder: SpIBinder) -> Option<Accessor> {
+        let inst = CString::new(instance).unwrap();
+
+        // Safety: All `SpIBinder` objects (the `binder` argument) hold a valid pointer
+        // to an `AIBinder` that is guaranteed to remain valid for the lifetime of the
+        // SpIBinder. `ABinderRpc_Accessor_fromBinder` creates a new pointer to that binder
+        // that it is responsible for.
+        // The `inst` argument is a new CString that will copied by
+        // `ABinderRpc_Accessor_fromBinder` and not modified.
+        let accessor =
+            unsafe { sys::ABinderRpc_Accessor_fromBinder(inst.as_ptr(), binder.as_raw()) };
+        if accessor.is_null() {
+            return None;
+        }
+        Some(Accessor { accessor })
+    }
+
+    /// Get the underlying binder for this Accessor for when it needs to be either
+    /// registered with service manager or sent to another process.
+    pub fn as_binder(&self) -> Option<SpIBinder> {
+        // Safety: `ABinderRpc_Accessor_asBinder` returns either a null pointer or a
+        // valid pointer to an owned `AIBinder`. Either of these values is safe to
+        // pass to `SpIBinder::from_raw`.
+        unsafe { SpIBinder::from_raw(sys::ABinderRpc_Accessor_asBinder(self.accessor)) }
+    }
+
+    /// Release the underlying ABinderRpc_Accessor pointer for use with the ndk API
+    /// This gives up ownership of the ABinderRpc_Accessor and it is the responsibility of
+    /// the caller to delete it with ABinderRpc_Accessor_delete
+    ///
+    /// # Safety
+    ///
+    /// - The returned `ABinderRpc_Accessor` pointer is now owned by the caller, who must
+    ///   call `ABinderRpc_Accessor_delete` to delete the object.
+    /// - This `Accessor` object is now useless after `release` so it can be dropped.
+    unsafe fn release(mut self) -> *mut sys::ABinderRpc_Accessor {
+        if self.accessor.is_null() {
+            log::error!("Attempting to release an Accessor that was already released");
+            return ptr::null_mut();
+        }
+        let ptr = self.accessor;
+        self.accessor = ptr::null_mut();
+        ptr
+    }
+
+    /// Callback invoked from C++ when the connection info is needed.
+    ///
+    /// # Safety
+    ///
+    /// - The `instance` parameter must be a non-null pointer to a valid C string for
+    ///   CStr::from_ptr. The memory must contain a valid null terminator at the end of
+    ///   the string within isize::MAX from the pointer. The memory must not be mutated for
+    ///   the duration of this function  call and must be valid for reads from the pointer
+    ///   to the null terminator.
+    /// - The `cookie` parameter must be the cookie for a `Box<F>` and
+    ///   the caller must hold a ref-count to it.
+    unsafe extern "C" fn connection_info<F>(
+        instance: *const c_char,
+        cookie: *mut c_void,
+    ) -> *mut binder_ndk_sys::ABinderRpc_ConnectionInfo
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        if cookie.is_null() || instance.is_null() {
+            log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
+            return ptr::null_mut();
+        }
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
+
+        // Safety: The caller in libbinder_ndk will have already verified this is a valid
+        // C string
+        let inst = unsafe {
+            match CStr::from_ptr(instance).to_str() {
+                Ok(s) => s,
+                Err(err) => {
+                    log::error!("Failed to get a valid C string! {err:?}");
+                    return ptr::null_mut();
+                }
+            }
+        };
+
+        let connection = match callback(inst) {
+            Some(con) => con,
+            None => {
+                return ptr::null_mut();
+            }
+        };
+
+        match connection {
+            ConnectionInfo::Vsock(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                unsafe {
+                    sys::ABinderRpc_ConnectionInfo_new(
+                        &addr as *const sockaddr_vm as *const sockaddr,
+                        mem::size_of::<sockaddr_vm>() as socklen_t,
+                    )
+                }
+            }
+            ConnectionInfo::Unix(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                // The cast is from sockaddr_un* to sockaddr*.
+                unsafe {
+                    sys::ABinderRpc_ConnectionInfo_new(
+                        &addr as *const sockaddr_un as *const sockaddr,
+                        mem::size_of::<sockaddr_un>() as socklen_t,
+                    )
+                }
+            }
+        }
+    }
+
+    /// Callback that drops the `Box<F>`.
+    /// This is invoked from C++ when a binder is unlinked.
+    ///
+    /// # Safety
+    ///
+    /// - The `cookie` parameter must be the cookie for a `Box<F>` and
+    ///   the owner must give up a ref-count to it.
+    unsafe extern "C" fn cookie_decr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        unsafe { std::mem::drop(Box::from_raw(cookie as *mut F)) };
+    }
+}
+
+impl Drop for Accessor {
+    fn drop(&mut self) {
+        if self.accessor.is_null() {
+            // This Accessor was already released.
+            return;
+        }
+        // Safety: `self.accessor` is always a valid, owned
+        // `ABinderRpc_Accessor` pointer returned by
+        // `ABinderRpc_Accessor_new` when `self` was created. This delete
+        // method can only be called once when `self` is dropped.
+        unsafe {
+            sys::ABinderRpc_Accessor_delete(self.accessor);
+        }
+    }
+}
+
+/// Register a new service with the default service manager.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn delegate_accessor(name: &str, mut binder: SpIBinder) -> Result<SpIBinder> {
+    let instance = CString::new(name).unwrap();
+    let mut delegator = ptr::null_mut();
+    let status =
+    // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
+    // string pointers. Caller retains ownership of both pointers.
+    // `AServiceManager_addService` creates a new strong reference and copies
+    // the string, so both pointers need only be valid until the call returns.
+        unsafe { sys::ABinderRpc_Accessor_delegateAccessor(instance.as_ptr(),
+            binder.as_native_mut(), &mut delegator) };
+
+    status_result(status)?;
+
+    // Safety: `delegator` is either null or a valid, owned pointer at this
+    // point, so can be safely passed to `SpIBinder::from_raw`.
+    Ok(unsafe { SpIBinder::from_raw(delegator).expect("Expected valid binder at this point") })
+}
+
+/// Rust wrapper around ABinderRpc_AccessorProvider objects for RPC binder service management.
+///
+/// Dropping the `AccessorProvider` will drop/unregister the underlying object.
+#[derive(Debug)]
+pub struct AccessorProvider {
+    accessor_provider: *mut sys::ABinderRpc_AccessorProvider,
+}
+
+/// Safety: A `AccessorProvider` is a wrapper around `ABinderRpc_AccessorProvider` which is
+/// `Sync` and `Send`. As
+/// `ABinderRpc_AccessorProvider` is threadsafe, this structure is too.
+/// The Fn owned the AccessorProvider has `Sync` and `Send` properties
+unsafe impl Send for AccessorProvider {}
+
+/// Safety: A `AccessorProvider` is a wrapper around `ABinderRpc_AccessorProvider` which is
+/// `Sync` and `Send`. As `ABinderRpc_AccessorProvider` is threadsafe, this structure is too.
+/// The Fn owned the AccessorProvider has `Sync` and `Send` properties
+unsafe impl Sync for AccessorProvider {}
+
+impl AccessorProvider {
+    /// Create a new `AccessorProvider` that will give libbinder `Accessors` in order to
+    /// connect to binder services over sockets.
+    ///
+    /// `instances` is a list of all instances that this `AccessorProvider` is responsible for.
+    /// It is declaring these instances as available to this process and will return
+    /// `Accessor` objects for them when libbinder calls the `provider` callback.
+    /// `provider` is the callback that libbinder will call when a service is being requested.
+    /// The callback takes a `&str` argument representing the service that is being requested.
+    /// See the `ABinderRpc_AccessorProvider_getAccessorCallback` for the C++ equivalent.
+    pub fn new<F>(instances: &[String], provider: F) -> Option<AccessorProvider>
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        let callback: *mut c_void = Box::into_raw(Box::new(provider)) as *mut c_void;
+        let c_str_instances: Vec<CString> =
+            instances.iter().map(|s| CString::new(s.as_bytes()).unwrap()).collect();
+        let mut c_instances: Vec<*const c_char> =
+            c_str_instances.iter().map(|s| s.as_ptr()).collect();
+        let num_instances: usize = c_instances.len();
+        // Safety:
+        // - The function pointer for the first argument is a valid `get_accessor` callback.
+        // - This call returns an owned `ABinderRpc_AccessorProvider` pointer which
+        //   must be destroyed via `ABinderRpc_unregisterAccessorProvider` when no longer
+        //   needed.
+        // - When the underlying ABinderRpc_AccessorProvider is deleted, it will call
+        //   the `cookie_decr_refcount` callback on the `callback` pointer to release its
+        //   strong ref.
+        // - The `c_instances` vector is not modified by the function
+        let accessor_provider = unsafe {
+            sys::ABinderRpc_registerAccessorProvider(
+                Some(Self::get_accessor::<F>),
+                c_instances.as_mut_ptr(),
+                num_instances,
+                callback,
+                Some(Self::accessor_cookie_decr_refcount::<F>),
+            )
+        };
+
+        if accessor_provider.is_null() {
+            return None;
+        }
+        Some(AccessorProvider { accessor_provider })
+    }
+
+    /// Callback invoked from C++ when an Accessor is needed.
+    ///
+    /// # Safety
+    ///
+    /// - libbinder guarantees the `instance` argument is a valid C string if it's not null.
+    /// - The `cookie` pointer is same pointer that we pass to ABinderRpc_registerAccessorProvider
+    ///   in AccessorProvider.new() which is the closure that we will delete with
+    ///   self.accessor_cookie_decr_refcount when unregistering the AccessorProvider.
+    unsafe extern "C" fn get_accessor<F>(
+        instance: *const c_char,
+        cookie: *mut c_void,
+    ) -> *mut binder_ndk_sys::ABinderRpc_Accessor
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        if cookie.is_null() || instance.is_null() {
+            log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
+            return ptr::null_mut();
+        }
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
+
+        let inst = {
+            // Safety: The caller in libbinder_ndk will have already verified this is a valid
+            // C string
+            match unsafe { CStr::from_ptr(instance) }.to_str() {
+                Ok(s) => s,
+                Err(err) => {
+                    log::error!("Failed to get a valid C string! {err:?}");
+                    return ptr::null_mut();
+                }
+            }
+        };
+
+        match callback(inst) {
+            Some(a) => {
+                // Safety: This is giving up ownership of this ABinderRpc_Accessor
+                // to the caller of this function (libbinder) and it is responsible
+                // for deleting it.
+                unsafe { a.release() }
+            }
+            None => ptr::null_mut(),
+        }
+    }
+
+    /// Callback that decrements the ref-count.
+    /// This is invoked from C++ when the provider is unregistered.
+    ///
+    /// # Safety
+    ///
+    /// - The `cookie` parameter must be the cookie for a `Box<F>` and
+    ///   the owner must give up a ref-count to it.
+    unsafe extern "C" fn accessor_cookie_decr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn(&str) -> Option<Accessor> + Send + Sync + 'static,
+    {
+        // Safety: The caller promises that `cookie` is for a Box<F>.
+        unsafe { std::mem::drop(Box::from_raw(cookie as *mut F)) };
+    }
+}
+
+impl Drop for AccessorProvider {
+    fn drop(&mut self) {
+        // Safety: `self.accessor_provider` is always a valid, owned
+        // `ABinderRpc_AccessorProvider` pointer returned by
+        // `ABinderRpc_registerAccessorProvider` when `self` was created. This delete
+        // method can only be called once when `self` is dropped.
+        unsafe {
+            sys::ABinderRpc_unregisterAccessorProvider(self.accessor_provider);
+        }
+    }
+}
diff --git a/libs/binder/rust/sys/BinderBindings.hpp b/libs/binder/rust/sys/BinderBindings.hpp
index 65fa2ca..c19e375 100644
--- a/libs/binder/rust/sys/BinderBindings.hpp
+++ b/libs/binder/rust/sys/BinderBindings.hpp
@@ -15,14 +15,20 @@
  */
 
 #include <android/binder_ibinder.h>
+#include <android/binder_parcel.h>
+#include <android/binder_status.h>
+#include <android/persistable_bundle.h>
+
+/* Platform only */
+#if defined(ANDROID_PLATFORM) || defined(__ANDROID_VENDOR__)
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
-#include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
 #include <android/binder_process.h>
+#include <android/binder_rpc.h>
 #include <android/binder_shell.h>
 #include <android/binder_stability.h>
-#include <android/binder_status.h>
+#endif
 
 namespace android {
 
@@ -80,8 +86,15 @@
 
 enum {
     FLAG_ONEWAY = FLAG_ONEWAY,
+#if defined(ANDROID_PLATFORM) || defined(__ANDROID_VENDOR__)
     FLAG_CLEAR_BUF = FLAG_CLEAR_BUF,
     FLAG_PRIVATE_LOCAL = FLAG_PRIVATE_LOCAL,
+#endif
+};
+
+enum {
+    APERSISTABLEBUNDLE_KEY_NOT_FOUND = APERSISTABLEBUNDLE_KEY_NOT_FOUND,
+    APERSISTABLEBUNDLE_ALLOCATOR_FAILED = APERSISTABLEBUNDLE_ALLOCATOR_FAILED,
 };
 
 } // namespace consts
diff --git a/libs/binder/rust/sys/Cargo.toml b/libs/binder/rust/sys/Cargo.toml
new file mode 100644
index 0000000..ad8e9c2
--- /dev/null
+++ b/libs/binder/rust/sys/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "android-binder-ndk-sys"
+version = "0.1.0"
+edition = "2021"
+description = "Bindgen bindings to android binder, restricted to the NDK"
+license = "Apache-2.0"
+
+[dependencies]
+
+[lib]
+path = "lib.rs"
+
+[build-dependencies]
+bindgen = "0.70.1"
diff --git a/libs/binder/rust/sys/build.rs b/libs/binder/rust/sys/build.rs
new file mode 100644
index 0000000..cb9c65b
--- /dev/null
+++ b/libs/binder/rust/sys/build.rs
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+use std::env;
+use std::path::PathBuf;
+
+fn main() {
+    let ndk_home = PathBuf::from(env::var("ANDROID_NDK_HOME").unwrap());
+    let toolchain = ndk_home.join("toolchains/llvm/prebuilt/linux-x86_64/");
+    let sysroot = toolchain.join("sysroot");
+    let bindings = bindgen::Builder::default()
+        .clang_arg(format!("--sysroot={}", sysroot.display()))
+        // TODO figure out what the "standard" #define is and use that instead
+        .header("BinderBindings.hpp")
+        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
+        // Keep in sync with libbinder_ndk_bindgen_flags.txt
+        .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: true })
+        .constified_enum("android::c_interface::consts::.*")
+        .allowlist_type("android::c_interface::.*")
+        .allowlist_type("AStatus")
+        .allowlist_type("AIBinder_Class")
+        .allowlist_type("AIBinder")
+        .allowlist_type("AIBinder_Weak")
+        .allowlist_type("AIBinder_DeathRecipient")
+        .allowlist_type("AParcel")
+        .allowlist_type("binder_status_t")
+        .blocklist_function("vprintf")
+        .blocklist_function("strtold")
+        .blocklist_function("_vtlog")
+        .blocklist_function("vscanf")
+        .blocklist_function("vfprintf_worker")
+        .blocklist_function("vsprintf")
+        .blocklist_function("vsnprintf")
+        .blocklist_function("vsnprintf_filtered")
+        .blocklist_function("vfscanf")
+        .blocklist_function("vsscanf")
+        .blocklist_function("vdprintf")
+        .blocklist_function("vasprintf")
+        .blocklist_function("strtold_l")
+        .allowlist_function(".*")
+        .generate()
+        .expect("Couldn't generate bindings");
+    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
+    bindings.write_to_file(out_path.join("bindings.rs")).expect("Couldn't write bindings.");
+    println!("cargo::rustc-link-lib=binder_ndk");
+}
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index 5352473..349e5a9 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -20,6 +20,7 @@
 use std::fmt;
 
 #[cfg(not(target_os = "trusty"))]
+#[allow(bad_style)]
 mod bindings {
     include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
 }
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 5359832..da4f128 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -384,8 +384,8 @@
     use std::time::Duration;
 
     use binder::{
-        BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode,
-        Strong,
+        Accessor, AccessorProvider, BinderFeatures, DeathRecipient, FromIBinder, IBinder,
+        Interface, SpIBinder, StatusCode, Strong,
     };
     // Import from impl API for testing only, should not be necessary as long as
     // you are using AIDL.
@@ -908,6 +908,164 @@
         assert_eq!(service.test().unwrap(), service_name);
     }
 
+    struct ToBeDeleted {
+        deleted: Arc<AtomicBool>,
+    }
+
+    impl Drop for ToBeDeleted {
+        fn drop(&mut self) {
+            assert!(!self.deleted.load(Ordering::Relaxed));
+            self.deleted.store(true, Ordering::Relaxed);
+        }
+    }
+
+    #[test]
+    fn test_accessor_callback_destruction() {
+        let deleted: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+        {
+            let accessor: Accessor;
+            {
+                let helper = ToBeDeleted { deleted: deleted.clone() };
+                let get_connection_info = move |_instance: &str| {
+                    // Capture this object so we can see it get destructed
+                    // after the parent scope
+                    let _ = &helper;
+                    None
+                };
+                accessor = Accessor::new("foo.service", get_connection_info);
+            }
+
+            match accessor.as_binder() {
+                Some(_) => {
+                    assert!(!deleted.load(Ordering::Relaxed));
+                }
+                None => panic!("failed to get that accessor binder"),
+            }
+        }
+        assert!(deleted.load(Ordering::Relaxed));
+    }
+
+    #[test]
+    fn test_accessor_delegator_new_each_time() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let delegator_binder =
+            binder::delegate_accessor("foo.service", accessor.as_binder().unwrap());
+        let delegator_binder2 =
+            binder::delegate_accessor("foo.service", accessor.as_binder().unwrap());
+
+        // The delegate_accessor creates new delegators each time
+        assert!(delegator_binder != delegator_binder2);
+    }
+
+    #[test]
+    fn test_accessor_delegate_the_delegator() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let delegator_binder =
+            binder::delegate_accessor("foo.service", accessor.as_binder().unwrap());
+        let delegator_binder2 =
+            binder::delegate_accessor("foo.service", delegator_binder.clone().unwrap());
+
+        assert!(delegator_binder.clone() == delegator_binder);
+        // The delegate_accessor creates new delegators each time. Even when they are delegators
+        // of delegators.
+        assert!(delegator_binder != delegator_binder2);
+    }
+
+    #[test]
+    fn test_accessor_delegator_wrong_name() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let delegator_binder =
+            binder::delegate_accessor("NOT.foo.service", accessor.as_binder().unwrap());
+        assert_eq!(delegator_binder, Err(StatusCode::NAME_NOT_FOUND));
+    }
+
+    #[test]
+    fn test_accessor_provider_simple() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_some());
+    }
+
+    #[test]
+    fn test_accessor_provider_no_instance() {
+        let instances: Vec<String> = vec![];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_none());
+    }
+
+    #[test]
+    fn test_accessor_provider_double_register() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor.is_some());
+        let accessor2 = AccessorProvider::new(&instances, move |_inst: &str| None);
+        assert!(accessor2.is_none());
+    }
+
+    #[test]
+    fn test_accessor_provider_register_drop_register() {
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        {
+            let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+            assert!(accessor.is_some());
+            // accessor drops and unregisters the provider
+        }
+        {
+            let accessor = AccessorProvider::new(&instances, move |_inst: &str| None);
+            assert!(accessor.is_some());
+        }
+    }
+
+    #[test]
+    fn test_accessor_provider_callback_destruction() {
+        let deleted: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+        let instances: Vec<String> = vec!["foo.service".to_owned(), "foo.other_service".to_owned()];
+        {
+            let accessor: Option<AccessorProvider>;
+            {
+                let helper = ToBeDeleted { deleted: deleted.clone() };
+                accessor = AccessorProvider::new(&instances, move |_inst: &str| {
+                    let _ = &helper;
+                    None
+                });
+            }
+            assert!(accessor.is_some());
+            assert!(!deleted.load(Ordering::Relaxed));
+        }
+        assert!(deleted.load(Ordering::Relaxed));
+    }
+
+    #[test]
+    fn test_accessor_from_accessor_binder() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let accessor2 =
+            Accessor::from_binder("foo.service", accessor.as_binder().unwrap()).unwrap();
+        assert_eq!(accessor.as_binder(), accessor2.as_binder());
+    }
+
+    #[test]
+    fn test_accessor_from_non_accessor_binder() {
+        let service_name = "rust_test_ibinder";
+        let _process = ScopedServiceProcess::new(service_name);
+        let binder = binder::get_service(service_name).unwrap();
+        assert!(binder.is_binder_alive());
+
+        let accessor = Accessor::from_binder("rust_test_ibinder", binder);
+        assert!(accessor.is_none());
+    }
+
+    #[test]
+    fn test_accessor_from_wrong_accessor_binder() {
+        let get_connection_info = move |_instance: &str| None;
+        let accessor = Accessor::new("foo.service", get_connection_info);
+        let accessor2 = Accessor::from_binder("NOT.foo.service", accessor.as_binder().unwrap());
+        assert!(accessor2.is_none());
+    }
+
     #[tokio::test]
     async fn reassociate_rust_binder_async() {
         let service_name = "testing_service";
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
index ce0f742..ee20a22 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -21,7 +21,8 @@
 
 use crate::read_utils::READ_FUNCS;
 use binder::binder_impl::{
-    Binder, BorrowedParcel, IBinderInternal, Parcel, Stability, TransactionCode,
+    Binder, BorrowedParcel, IBinderInternal, LocalStabilityType, Parcel, TransactionCode,
+    VintfStabilityType,
 };
 use binder::{
     declare_binder_interface, BinderFeatures, Interface, Parcelable, ParcelableHolder, SpIBinder,
@@ -121,13 +122,15 @@
             }
 
             ReadOperation::ReadParcelableHolder { is_vintf } => {
-                let stability = if is_vintf { Stability::Vintf } else { Stability::Local };
-                let mut holder: ParcelableHolder = ParcelableHolder::new(stability);
-                match holder.read_from_parcel(parcel.borrowed_ref()) {
-                    Ok(result) => result,
-                    Err(err) => {
-                        println!("error occurred while reading from parcel: {:?}", err)
-                    }
+                let result = if is_vintf {
+                    ParcelableHolder::<VintfStabilityType>::new()
+                        .read_from_parcel(parcel.borrowed_ref())
+                } else {
+                    ParcelableHolder::<LocalStabilityType>::new()
+                        .read_from_parcel(parcel.borrowed_ref())
+                };
+                if let Err(e) = result {
+                    println!("error occurred while reading from parcel: {e:?}")
                 }
             }
 
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
index 2b6c282..a902e96 100644
--- a/libs/binder/rust/tests/serialization.rs
+++ b/libs/binder/rust/tests/serialization.rs
@@ -124,7 +124,7 @@
         bindings::Transaction_TEST_BYTE => {
             assert_eq!(parcel.read::<i8>()?, 0);
             assert_eq!(parcel.read::<i8>()?, 1);
-            assert_eq!(parcel.read::<i8>()?, i8::max_value());
+            assert_eq!(parcel.read::<i8>()?, i8::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i8>>()?, unsafe { bindings::TESTDATA_I8 });
             // SAFETY: Just reading an extern constant.
@@ -133,7 +133,7 @@
 
             reply.write(&0i8)?;
             reply.write(&1i8)?;
-            reply.write(&i8::max_value())?;
+            reply.write(&i8::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I8 }[..])?;
             // SAFETY: Just reading an extern constant.
@@ -143,14 +143,14 @@
         bindings::Transaction_TEST_U16 => {
             assert_eq!(parcel.read::<u16>()?, 0);
             assert_eq!(parcel.read::<u16>()?, 1);
-            assert_eq!(parcel.read::<u16>()?, u16::max_value());
+            assert_eq!(parcel.read::<u16>()?, u16::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<u16>>()?, unsafe { bindings::TESTDATA_CHARS });
             assert_eq!(parcel.read::<Option<Vec<u16>>>()?, None);
 
             reply.write(&0u16)?;
             reply.write(&1u16)?;
-            reply.write(&u16::max_value())?;
+            reply.write(&u16::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_CHARS }[..])?;
             reply.write(&(None as Option<Vec<u16>>))?;
@@ -158,14 +158,14 @@
         bindings::Transaction_TEST_I32 => {
             assert_eq!(parcel.read::<i32>()?, 0);
             assert_eq!(parcel.read::<i32>()?, 1);
-            assert_eq!(parcel.read::<i32>()?, i32::max_value());
+            assert_eq!(parcel.read::<i32>()?, i32::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i32>>()?, unsafe { bindings::TESTDATA_I32 });
             assert_eq!(parcel.read::<Option<Vec<i32>>>()?, None);
 
             reply.write(&0i32)?;
             reply.write(&1i32)?;
-            reply.write(&i32::max_value())?;
+            reply.write(&i32::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I32 }[..])?;
             reply.write(&(None as Option<Vec<i32>>))?;
@@ -173,14 +173,14 @@
         bindings::Transaction_TEST_I64 => {
             assert_eq!(parcel.read::<i64>()?, 0);
             assert_eq!(parcel.read::<i64>()?, 1);
-            assert_eq!(parcel.read::<i64>()?, i64::max_value());
+            assert_eq!(parcel.read::<i64>()?, i64::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<i64>>()?, unsafe { bindings::TESTDATA_I64 });
             assert_eq!(parcel.read::<Option<Vec<i64>>>()?, None);
 
             reply.write(&0i64)?;
             reply.write(&1i64)?;
-            reply.write(&i64::max_value())?;
+            reply.write(&i64::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_I64 }[..])?;
             reply.write(&(None as Option<Vec<i64>>))?;
@@ -188,14 +188,14 @@
         bindings::Transaction_TEST_U64 => {
             assert_eq!(parcel.read::<u64>()?, 0);
             assert_eq!(parcel.read::<u64>()?, 1);
-            assert_eq!(parcel.read::<u64>()?, u64::max_value());
+            assert_eq!(parcel.read::<u64>()?, u64::MAX);
             // SAFETY: Just reading an extern constant.
             assert_eq!(parcel.read::<Vec<u64>>()?, unsafe { bindings::TESTDATA_U64 });
             assert_eq!(parcel.read::<Option<Vec<u64>>>()?, None);
 
             reply.write(&0u64)?;
             reply.write(&1u64)?;
-            reply.write(&u64::max_value())?;
+            reply.write(&u64::MAX)?;
             // SAFETY: Just reading an extern constant.
             reply.write(&unsafe { bindings::TESTDATA_U64 }[..])?;
             reply.write(&(None as Option<Vec<u64>>))?;
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index be99065..78fe2a8 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -127,7 +127,12 @@
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
-    android::binder::Status checkService(const std::string&, android::os::Service*) override {
+    android::binder::Status checkService(const std::string&,
+                                         android::sp<android::IBinder>*) override {
+        // We can't send BpBinder for regular binder over RPC.
+        return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
+    }
+    android::binder::Status checkService2(const std::string&, android::os::Service*) override {
         // We can't send BpBinder for regular binder over RPC.
         return android::binder::Status::fromStatusT(android::INVALID_OPERATION);
     }
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index dd50fbd..f412dfb 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -42,12 +42,43 @@
     defaults: ["binder_test_defaults"],
     header_libs: ["libbinder_headers"],
     srcs: ["binderDriverInterfaceTest.cpp"],
+    shared_libs: [
+        "libbinder",
+    ],
     test_suites: [
-        "device-tests",
+        "general-tests",
         "vts",
     ],
 }
 
+cc_test {
+    name: "binderCacheUnitTest",
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    srcs: [
+        "binderCacheUnitTest.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+        "libbinder",
+        "libcutils",
+        "libutils",
+    ],
+    static_libs: [
+        "libfakeservicemanager",
+    ],
+    defaults: [
+        "libbinder_client_cache_flag",
+        "libbinder_addservice_cache_flag",
+        "libbinder_remove_cache_static_list_flag",
+    ],
+    test_suites: ["general-tests"],
+    require_root: true,
+}
+
 // unit test only, which can run on host and doesn't use /dev/binder
 cc_test {
     name: "binderUnitTest",
@@ -127,13 +158,14 @@
         "libbase",
         "libbinder",
         "liblog",
+        "libprocessgroup",
         "libutils",
     ],
     static_libs: [
         "libgmock",
     ],
     test_suites: [
-        "device-tests",
+        "general-tests",
         "vts",
     ],
     require_root: true,
@@ -504,6 +536,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_test {
@@ -700,8 +735,11 @@
         "liblog",
         "libutils",
     ],
+    static_libs: [
+        "libgmock",
+    ],
     test_suites: [
-        "device-tests",
+        "general-tests",
         "vts",
     ],
     require_root: true,
@@ -758,7 +796,29 @@
     ],
 
     test_suites: [
-        "device-tests",
+        "general-tests",
+        "vts",
+    ],
+    require_root: true,
+}
+
+cc_test {
+    name: "binderStabilityIntegrationTest",
+    defaults: ["binder_test_defaults"],
+    srcs: [
+        "binderStabilityIntegrationTest.cpp",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "libutils",
+    ],
+    static_libs: [
+        "libprocpartition",
+    ],
+
+    test_suites: [
+        "general-tests",
         "vts",
     ],
     require_root: true,
@@ -884,6 +944,7 @@
             enabled: false,
         },
     },
+    corpus: ["corpus/*"],
     fuzz_config: {
         cc: [
             "smoreland@google.com",
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index 1164767..dcd6461 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -34,6 +34,8 @@
     void holdBinder(@nullable IBinder binder);
     @nullable IBinder getHeldBinder();
 
+    byte[] repeatBytes(in byte[] bytes);
+
     // Idea is client creates its own instance of IBinderRpcTest and calls this,
     // and the server calls 'binder' with (calls - 1) passing itself as 'binder',
     // going back and forth until calls = 0
diff --git a/libs/binder/tests/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
index c0c0aae..339ce4b 100644
--- a/libs/binder/tests/binderAllocationLimits.cpp
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -22,17 +22,33 @@
 #include <binder/RpcServer.h>
 #include <binder/RpcSession.h>
 #include <cutils/trace.h>
+#include <gtest/gtest-spi.h>
 #include <gtest/gtest.h>
 #include <utils/CallStack.h>
 
 #include <malloc.h>
+#include <atomic>
 #include <functional>
+#include <numeric>
 #include <vector>
 
 using namespace android::binder::impl;
 
 static android::String8 gEmpty(""); // make sure first allocation from optimization runs
 
+struct State {
+    State(std::vector<size_t>&& expectedMallocs) : expectedMallocs(std::move(expectedMallocs)) {}
+    ~State() {
+        size_t num = numMallocs.load();
+        if (expectedMallocs.size() != num) {
+            ADD_FAILURE() << "Expected " << expectedMallocs.size() << " allocations, but got "
+                          << num;
+        }
+    }
+    const std::vector<size_t> expectedMallocs;
+    std::atomic<size_t> numMallocs;
+};
+
 struct DestructionAction {
     DestructionAction(std::function<void()> f) : mF(std::move(f)) {}
     ~DestructionAction() { mF(); };
@@ -95,8 +111,7 @@
 
 // Action to execute when malloc is hit. Supports nesting. Malloc is not
 // restricted when the allocation hook is being processed.
-__attribute__((warn_unused_result))
-DestructionAction OnMalloc(LambdaHooks::AllocationHook f) {
+__attribute__((warn_unused_result)) DestructionAction OnMalloc(LambdaHooks::AllocationHook f) {
     MallocHooks before = MallocHooks::save();
     LambdaHooks::lambdas.emplace_back(std::move(f));
     LambdaHooks::lambda_malloc_hooks.overwrite();
@@ -106,6 +121,22 @@
     });
 }
 
+DestructionAction setExpectedMallocs(std::vector<size_t>&& expected) {
+    auto state = std::make_shared<State>(std::move(expected));
+    return OnMalloc([state = state](size_t bytes) {
+        size_t num = state->numMallocs.fetch_add(1);
+        if (num >= state->expectedMallocs.size() || state->expectedMallocs[num] != bytes) {
+            ADD_FAILURE() << "Unexpected allocation number " << num << " of size " << bytes
+                          << " bytes" << std::endl
+                          << android::CallStack::stackToString("UNEXPECTED ALLOCATION",
+                                                               android::CallStack::getCurrent(
+                                                                       4 /*ignoreDepth*/)
+                                                                       .get())
+                          << std::endl;
+        }
+    });
+}
+
 // exported symbol, to force compiler not to optimize away pointers we set here
 const void* imaginary_use;
 
@@ -119,16 +150,53 @@
 
         imaginary_use = new int[10];
     }
+    delete[] reinterpret_cast<const int*>(imaginary_use);
     EXPECT_EQ(mallocs, 1u);
 }
 
+TEST(TestTheTest, OnMallocWithExpectedMallocs) {
+    std::vector<size_t> expectedMallocs = {
+            4,
+            16,
+            8,
+    };
+    {
+        const auto on_malloc = setExpectedMallocs(std::move(expectedMallocs));
+        imaginary_use = new int32_t[1];
+        delete[] reinterpret_cast<const int*>(imaginary_use);
+        imaginary_use = new int32_t[4];
+        delete[] reinterpret_cast<const int*>(imaginary_use);
+        imaginary_use = new int32_t[2];
+        delete[] reinterpret_cast<const int*>(imaginary_use);
+    }
+}
+
+TEST(TestTheTest, OnMallocWithExpectedMallocsWrongSize) {
+    std::vector<size_t> expectedMallocs = {
+            4,
+            16,
+            100000,
+    };
+    EXPECT_NONFATAL_FAILURE(
+            {
+                const auto on_malloc = setExpectedMallocs(std::move(expectedMallocs));
+                imaginary_use = new int32_t[1];
+                delete[] reinterpret_cast<const int*>(imaginary_use);
+                imaginary_use = new int32_t[4];
+                delete[] reinterpret_cast<const int*>(imaginary_use);
+                imaginary_use = new int32_t[2];
+                delete[] reinterpret_cast<const int*>(imaginary_use);
+            },
+            "Unexpected allocation number 2 of size 8 bytes");
+}
 
 __attribute__((warn_unused_result))
 DestructionAction ScopeDisallowMalloc() {
     return OnMalloc([&](size_t bytes) {
-        ADD_FAILURE() << "Unexpected allocation: " << bytes;
+        FAIL() << "Unexpected allocation: " << bytes;
         using android::CallStack;
-        std::cout << CallStack::stackToString("UNEXPECTED ALLOCATION", CallStack::getCurrent(4 /*ignoreDepth*/).get())
+        std::cout << CallStack::stackToString("UNEXPECTED ALLOCATION",
+                                              CallStack::getCurrent(4 /*ignoreDepth*/).get())
                   << std::endl;
     });
 }
@@ -224,6 +292,51 @@
     EXPECT_EQ(mallocs, 1u);
 }
 
+TEST(BinderAccessorAllocation, AddAccessorCheckService) {
+    // Need to call defaultServiceManager() before checking malloc because it
+    // will allocate an instance in the call_once
+    const auto sm = defaultServiceManager();
+    const std::string kInstanceName1 = "foo.bar.IFoo/default";
+    const std::string kInstanceName2 = "foo.bar.IFoo2/default";
+    const String16 kInstanceName16(kInstanceName1.c_str());
+    std::vector<size_t> expectedMallocs = {
+            // addAccessorProvider
+            112, // new AccessorProvider
+            16,  // new AccessorProviderEntry
+            // checkService
+            45,  // String8 from String16 in CppShim::checkService
+            128, // writeInterfaceToken
+            16,  // getInjectedAccessor, new AccessorProviderEntry
+            66,  // getInjectedAccessor, String16
+            45,  // String8 from String16 in AccessorProvider::provide
+    };
+    std::set<std::string> supportedInstances = {kInstanceName1, kInstanceName2};
+    auto onMalloc = setExpectedMallocs(std::move(expectedMallocs));
+
+    auto receipt =
+            android::addAccessorProvider(std::move(supportedInstances),
+                                         [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+
+    sp<IBinder> binder = sm->checkService(kInstanceName16);
+
+    status_t status = android::removeAccessorProvider(receipt);
+}
+
+TEST(BinderAccessorAllocation, AddAccessorEmpty) {
+    std::vector<size_t> expectedMallocs = {
+            48, // From ALOGE with empty set of instances
+    };
+    std::set<std::string> supportedInstances = {};
+    auto onMalloc = setExpectedMallocs(std::move(expectedMallocs));
+
+    auto receipt =
+            android::addAccessorProvider(std::move(supportedInstances),
+                                         [&](const String16&) -> sp<IBinder> { return nullptr; });
+
+    EXPECT_TRUE(receipt.expired());
+}
+
 TEST(RpcBinderAllocation, SetupRpcServer) {
     std::string tmp = getenv("TMPDIR") ?: "/tmp";
     std::string addr = tmp + "/binderRpcBenchmark";
@@ -255,6 +368,7 @@
 }
 
 int main(int argc, char** argv) {
+    LOG(INFO) << "Priming static log variables for binderAllocationLimits.";
     if (getenv("LIBC_HOOKS_ENABLE") == nullptr) {
         CHECK(0 == setenv("LIBC_HOOKS_ENABLE", "1", true /*overwrite*/));
         execv(argv[0], argv);
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
new file mode 100644
index 0000000..121e5ae
--- /dev/null
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <gtest/gtest.h>
+
+#include <android-base/logging.h>
+#include <android/os/IServiceManager.h>
+#include <binder/IBinder.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/IServiceManagerUnitTestHelper.h>
+#include "fakeservicemanager/FakeServiceManager.h"
+
+#include <sys/prctl.h>
+#include <thread>
+
+using namespace android;
+
+#ifdef LIBBINDER_CLIENT_CACHE
+constexpr bool kUseLibbinderCache = true;
+#else
+constexpr bool kUseLibbinderCache = false;
+#endif
+
+#ifdef LIBBINDER_ADDSERVICE_CACHE
+constexpr bool kUseCacheInAddService = true;
+#else
+constexpr bool kUseCacheInAddService = false;
+#endif
+
+#ifdef LIBBINDER_REMOVE_CACHE_STATIC_LIST
+constexpr bool kRemoveStaticList = true;
+#else
+constexpr bool kRemoveStaticList = false;
+#endif
+
+// A service name which is in the static list of cachable services
+const String16 kCachedServiceName = String16("isub");
+
+#define EXPECT_OK(status)                 \
+    do {                                  \
+        binder::Status stat = (status);   \
+        EXPECT_TRUE(stat.isOk()) << stat; \
+    } while (false)
+
+const String16 kServerName = String16("binderCacheUnitTest");
+
+class FooBar : public BBinder {
+public:
+    status_t onTransact(uint32_t, const Parcel&, Parcel*, uint32_t) {
+        // exit the server
+        std::thread([] { exit(EXIT_FAILURE); }).detach();
+        return OK;
+    }
+    void killServer(sp<IBinder> binder) {
+        Parcel data, reply;
+        binder->transact(0, data, &reply, 0);
+    }
+};
+
+class MockAidlServiceManager : public os::IServiceManagerDefault {
+public:
+    MockAidlServiceManager() : innerSm() {}
+
+    binder::Status checkService2(const ::std::string& name, os::Service* _out) override {
+        os::ServiceWithMetadata serviceWithMetadata = os::ServiceWithMetadata();
+        serviceWithMetadata.service = innerSm.getService(String16(name.c_str()));
+        serviceWithMetadata.isLazyService = false;
+        *_out = os::Service::make<os::Service::Tag::serviceWithMetadata>(serviceWithMetadata);
+        return binder::Status::ok();
+    }
+
+    binder::Status addService(const std::string& name, const sp<IBinder>& service,
+                              bool allowIsolated, int32_t dumpPriority) override {
+        return binder::Status::fromStatusT(
+                innerSm.addService(String16(name.c_str()), service, allowIsolated, dumpPriority));
+    }
+
+    void clearServices() { innerSm.clear(); }
+
+    FakeServiceManager innerSm;
+};
+
+// Returns services with isLazyService flag as true.
+class MockAidlServiceManager2 : public os::IServiceManagerDefault {
+public:
+    MockAidlServiceManager2() : innerSm() {}
+
+    binder::Status checkService2(const ::std::string& name, os::Service* _out) override {
+        os::ServiceWithMetadata serviceWithMetadata = os::ServiceWithMetadata();
+        serviceWithMetadata.service = innerSm.getService(String16(name.c_str()));
+        serviceWithMetadata.isLazyService = true;
+        *_out = os::Service::make<os::Service::Tag::serviceWithMetadata>(serviceWithMetadata);
+        return binder::Status::ok();
+    }
+
+    binder::Status addService(const std::string& name, const sp<IBinder>& service,
+                              bool allowIsolated, int32_t dumpPriority) override {
+        return binder::Status::fromStatusT(
+                innerSm.addService(String16(name.c_str()), service, allowIsolated, dumpPriority));
+    }
+
+    void clearServices() { innerSm.clear(); }
+
+    FakeServiceManager innerSm;
+};
+
+class LibbinderCacheRemoveStaticList : public ::testing::Test {
+protected:
+    void SetUp() override {
+        fakeServiceManager = sp<MockAidlServiceManager2>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(true);
+    }
+    void TearDown() override {}
+
+public:
+    void cacheAddServiceAndConfirmCacheMiss(const sp<IBinder>& binder1) {
+        // Add a service. This shouldn't cache it.
+        EXPECT_EQ(OK,
+                  mServiceManager->addService(kCachedServiceName, binder1,
+                                              /*allowIsolated = */ false,
+                                              android::os::IServiceManager::FLAG_IS_LAZY_SERVICE));
+        // Try to populate cache. Cache shouldn't be updated.
+        EXPECT_EQ(binder1, mServiceManager->checkService(kCachedServiceName));
+        fakeServiceManager->clearServices();
+        EXPECT_EQ(nullptr, mServiceManager->checkService(kCachedServiceName));
+    }
+
+    sp<MockAidlServiceManager2> fakeServiceManager;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+TEST_F(LibbinderCacheRemoveStaticList, AddLocalServiceAndConfirmCacheMiss) {
+    if (!kRemoveStaticList) {
+        GTEST_SKIP() << "Skipping as feature is not enabled";
+        return;
+    }
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    cacheAddServiceAndConfirmCacheMiss(binder1);
+}
+
+TEST_F(LibbinderCacheRemoveStaticList, AddRemoteServiceAndConfirmCacheMiss) {
+    if (!kRemoveStaticList) {
+        GTEST_SKIP() << "Skipping as feature is not enabled";
+        return;
+    }
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    ASSERT_NE(binder1, nullptr);
+    cacheAddServiceAndConfirmCacheMiss(binder1);
+}
+
+class LibbinderCacheAddServiceTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(true);
+    }
+
+    void TearDown() override {}
+
+public:
+    void cacheAddServiceAndConfirmCacheHit(const sp<IBinder>& binder1) {
+        // Add a service. This also caches it.
+        EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder1));
+        // remove services from fakeservicemanager
+        fakeServiceManager->clearServices();
+
+        sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+        if (kUseCacheInAddService && kUseLibbinderCache) {
+            // If cache is enabled, we should get the binder.
+            EXPECT_EQ(binder1, result);
+        } else {
+            // If cache is disabled, then we should get the null binder
+            EXPECT_EQ(nullptr, result);
+        }
+    }
+    sp<MockAidlServiceManager> fakeServiceManager;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+TEST_F(LibbinderCacheAddServiceTest, AddLocalServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
+TEST_F(LibbinderCacheAddServiceTest, AddRemoteServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    ASSERT_NE(binder1, nullptr);
+    cacheAddServiceAndConfirmCacheHit(binder1);
+}
+
+class LibbinderCacheTest : public ::testing::Test {
+protected:
+    void SetUp() override {
+        fakeServiceManager = sp<MockAidlServiceManager>::make();
+        mServiceManager = getServiceManagerShimFromAidlServiceManagerForTests(fakeServiceManager);
+        mServiceManager->enableAddServiceCache(false);
+    }
+
+    void TearDown() override {}
+
+public:
+    void cacheAndConfirmCacheHit(const sp<IBinder>& binder1, const sp<IBinder>& binder2) {
+        // Add a service
+        EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder1));
+        // Get the service. This caches it.
+        sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+        ASSERT_EQ(binder1, result);
+
+        // Add the different binder and replace the service.
+        // The cache should still hold the original binder.
+        EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder2));
+
+        result = mServiceManager->checkService(kCachedServiceName);
+        if (kUseLibbinderCache) {
+            // If cache is enabled, we should get the binder to Service Manager.
+            EXPECT_EQ(binder1, result);
+        } else {
+            // If cache is disabled, then we should get the newer binder
+            EXPECT_EQ(binder2, result);
+        }
+    }
+
+    sp<MockAidlServiceManager> fakeServiceManager;
+    sp<android::IServiceManager> mServiceManager;
+};
+
+TEST_F(LibbinderCacheTest, AddLocalServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    sp<IBinder> binder2 = sp<BBinder>::make();
+
+    cacheAndConfirmCacheHit(binder1, binder2);
+}
+
+TEST_F(LibbinderCacheTest, AddRemoteServiceAndConfirmCacheHit) {
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    ASSERT_NE(binder1, nullptr);
+    sp<IBinder> binder2 = IInterface::asBinder(mServiceManager);
+
+    cacheAndConfirmCacheHit(binder1, binder2);
+}
+
+TEST_F(LibbinderCacheTest, RemoveFromCacheOnServerDeath) {
+    sp<IBinder> binder1 = defaultServiceManager()->checkService(kServerName);
+    FooBar foo = FooBar();
+
+    EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder1));
+
+    // Check Service, this caches the binder
+    sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+    ASSERT_EQ(binder1, result);
+
+    // Kill the server, this should remove from cache.
+    pid_t pid;
+    ASSERT_EQ(OK, binder1->getDebugPid(&pid));
+    foo.killServer(binder1);
+    system(("kill -9 " + std::to_string(pid)).c_str());
+
+    sp<IBinder> binder2 = sp<BBinder>::make();
+
+    // Add new service with the same name.
+    // This will replace the service in FakeServiceManager.
+    EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder2));
+
+    // Confirm that new service is returned instead of old.
+    int retry_count = 20;
+    sp<IBinder> result2;
+    do {
+        std::this_thread::sleep_for(std::chrono::milliseconds(50));
+        if (retry_count-- == 0) {
+            break;
+        }
+        result2 = mServiceManager->checkService(kCachedServiceName);
+    } while (result2 != binder2);
+
+    ASSERT_EQ(binder2, result2);
+}
+
+TEST_F(LibbinderCacheTest, NullBinderNotCached) {
+    sp<IBinder> binder1 = nullptr;
+    sp<IBinder> binder2 = sp<BBinder>::make();
+
+    // Check for a cacheble service which isn't registered.
+    // FakeServiceManager should return nullptr.
+    // This shouldn't be cached.
+    sp<IBinder> result = mServiceManager->checkService(kCachedServiceName);
+    ASSERT_EQ(binder1, result);
+
+    // Add the same service
+    EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder2));
+
+    // This should return the newly added service.
+    result = mServiceManager->checkService(kCachedServiceName);
+    EXPECT_EQ(binder2, result);
+}
+
+// TODO(b/333854840): Remove this test removing the static list
+TEST_F(LibbinderCacheTest, DoNotCacheServiceNotInList) {
+    if (kRemoveStaticList) {
+        GTEST_SKIP() << "Skipping test as static list is disabled";
+        return;
+    }
+
+    sp<IBinder> binder1 = sp<BBinder>::make();
+    sp<IBinder> binder2 = sp<BBinder>::make();
+    String16 serviceName = String16("NewLibbinderCacheTest");
+    // Add a service
+    EXPECT_EQ(OK, mServiceManager->addService(serviceName, binder1));
+    // Get the service. This shouldn't caches it.
+    sp<IBinder> result = mServiceManager->checkService(serviceName);
+    ASSERT_EQ(binder1, result);
+
+    // Add the different binder and replace the service.
+    EXPECT_EQ(OK, mServiceManager->addService(serviceName, binder2));
+
+    // Confirm that we get the new service
+    result = mServiceManager->checkService(serviceName);
+    EXPECT_EQ(binder2, result);
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+
+    if (fork() == 0) {
+        prctl(PR_SET_PDEATHSIG, SIGHUP);
+
+        // Start a FooBar service and add it to the servicemanager.
+        sp<IBinder> server = new FooBar();
+        defaultServiceManager()->addService(kServerName, server);
+
+        IPCThreadState::self()->joinThreadPool(true);
+        exit(1); // should not reach
+    }
+
+    status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(3);
+    ProcessState::self()->startThreadPool();
+    CHECK_EQ(ProcessState::self()->isThreadPoolStarted(), true);
+    CHECK_GT(ProcessState::self()->getThreadPoolMaxTotalThreadCount(), 0);
+
+    auto binder = defaultServiceManager()->waitForService(kServerName);
+    CHECK_NE(nullptr, binder.get());
+    return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/tests/binderDriverInterfaceTest.cpp b/libs/binder/tests/binderDriverInterfaceTest.cpp
index 7be4f21..af82860 100644
--- a/libs/binder/tests/binderDriverInterfaceTest.cpp
+++ b/libs/binder/tests/binderDriverInterfaceTest.cpp
@@ -20,12 +20,15 @@
 #include <stdlib.h>
 
 #include <binder/IBinder.h>
+#include <binder/ProcessState.h>
 #include <gtest/gtest.h>
 #include <linux/android/binder.h>
 #include <poll.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 
+#include "../binder_module.h"
+
 #define BINDER_DEV_NAME "/dev/binder"
 
 testing::Environment* binder_env;
@@ -365,6 +368,251 @@
     binderTestReadEmpty();
 }
 
+TEST_F(BinderDriverInterfaceTest, RequestFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+    } __attribute__((packed)) bc1 = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t cmd0;
+        // Expecting a BR_FROZEN_BINDER since BC_REQUEST_FREEZE_NOTIFICATION
+        // above should lead to an immediate notification of the current state.
+        uint32_t cmd1;
+        struct binder_frozen_state_info arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br1;
+    struct {
+        uint32_t cmd2;
+        binder_uintptr_t arg2;
+        uint32_t cmd3;
+        struct binder_handle_cookie arg3;
+        uint32_t cmd4;
+        uint32_t arg4;
+    } __attribute__((packed)) bc2 = {
+            // Tell kernel that userspace has done handling BR_FROZEN_BINDER.
+            .cmd2 = BC_FREEZE_NOTIFICATION_DONE,
+            .arg2 = cookie,
+            .cmd3 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg3 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            .cmd4 = BC_DECREFS,
+            .arg4 = 0,
+    };
+    struct {
+        uint32_t cmd2;
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t pad[16];
+    } __attribute__((packed)) br2;
+
+    struct binder_write_read bwr1 = binder_write_read();
+    bwr1.write_buffer = (uintptr_t)&bc1;
+    bwr1.write_size = sizeof(bc1);
+    bwr1.read_buffer = (uintptr_t)&br1;
+    bwr1.read_size = sizeof(br1);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr1);
+    EXPECT_EQ(sizeof(bc1), bwr1.write_consumed);
+    EXPECT_EQ(sizeof(br1) - sizeof(br1.pad), bwr1.read_consumed);
+    EXPECT_EQ(BR_NOOP, br1.cmd0);
+    ASSERT_EQ(BR_FROZEN_BINDER, br1.cmd1);
+    EXPECT_FALSE(br1.arg1.is_frozen);
+
+    struct binder_write_read bwr2 = binder_write_read();
+    bwr2.write_buffer = (uintptr_t)&bc2;
+    bwr2.write_size = sizeof(bc2);
+    bwr2.read_buffer = (uintptr_t)&br2;
+    bwr2.read_size = sizeof(br2);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr2);
+    EXPECT_EQ(sizeof(bc2), bwr2.write_consumed);
+    EXPECT_EQ(sizeof(br2) - sizeof(br2.pad), bwr2.read_consumed);
+    EXPECT_EQ(BR_NOOP, br2.cmd2);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br2.cmd3);
+    EXPECT_EQ(cookie, br2.arg3);
+
+    binderTestReadEmpty();
+}
+
+TEST_F(BinderDriverInterfaceTest, OverwritePendingFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+        uint32_t cmd2;
+        struct binder_handle_cookie arg2;
+        uint32_t cmd3;
+        uint32_t arg3;
+    } __attribute__((packed)) bc = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            // This BC_REQUEST_FREEZE_NOTIFICATION should lead to a pending
+            // frozen notification inserted into the queue.
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            // Send BC_CLEAR_FREEZE_NOTIFICATION before the above frozen
+            // notification has a chance of being sent. The notification should
+            // be overwritten. Userspace is expected to only receive
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE.
+            .cmd2 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg2 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+            .cmd3 = BC_DECREFS,
+            .arg3 = 0,
+    };
+    struct {
+        uint32_t cmd0;
+        uint32_t cmd1;
+        binder_uintptr_t arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br;
+    struct binder_write_read bwr = binder_write_read();
+
+    bwr.write_buffer = (uintptr_t)&bc;
+    bwr.write_size = sizeof(bc);
+    bwr.read_buffer = (uintptr_t)&br;
+    bwr.read_size = sizeof(br);
+
+    binderTestIoctl(BINDER_WRITE_READ, &bwr);
+    EXPECT_EQ(sizeof(bc), bwr.write_consumed);
+    EXPECT_EQ(sizeof(br) - sizeof(br.pad), bwr.read_consumed);
+    EXPECT_EQ(BR_NOOP, br.cmd0);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br.cmd1);
+    EXPECT_EQ(cookie, br.arg1);
+    binderTestReadEmpty();
+}
+
+TEST_F(BinderDriverInterfaceTest, ResendFrozenNotification) {
+    if (!android::ProcessState::isDriverFeatureEnabled(
+                android::ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support freeze notification";
+        return;
+    }
+    binder_uintptr_t cookie = 1234;
+    struct {
+        uint32_t cmd0;
+        uint32_t arg0;
+        uint32_t cmd1;
+        struct binder_handle_cookie arg1;
+    } __attribute__((packed)) bc1 = {
+            .cmd0 = BC_INCREFS,
+            .arg0 = 0,
+            .cmd1 = BC_REQUEST_FREEZE_NOTIFICATION,
+            .arg1 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t cmd0;
+        uint32_t cmd1;
+        struct binder_frozen_state_info arg1;
+        uint32_t pad[16];
+    } __attribute__((packed)) br1;
+    struct {
+        uint32_t cmd2;
+        struct binder_handle_cookie arg2;
+    } __attribute__((packed)) bc2 = {
+            // Clear the notification before acknowledging the in-flight
+            // BR_FROZEN_BINDER. Kernel should hold off sending
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE until the acknowledgement
+            // reaches kernel.
+            .cmd2 = BC_CLEAR_FREEZE_NOTIFICATION,
+            .arg2 =
+                    {
+                            .handle = 0,
+                            .cookie = cookie,
+                    },
+    };
+    struct {
+        uint32_t pad[16];
+    } __attribute__((packed)) br2;
+    struct {
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t cmd4;
+        uint32_t arg4;
+    } __attribute__((packed)) bc3 = {
+            // Send the acknowledgement. Now the kernel should send out
+            // BR_CLEAR_FREEZE_NOTIFICATION_DONE.
+            .cmd3 = BC_FREEZE_NOTIFICATION_DONE,
+            .arg3 = cookie,
+            .cmd4 = BC_DECREFS,
+            .arg4 = 0,
+    };
+    struct {
+        uint32_t cmd2;
+        uint32_t cmd3;
+        binder_uintptr_t arg3;
+        uint32_t pad[16];
+    } __attribute__((packed)) br3;
+
+    struct binder_write_read bwr1 = binder_write_read();
+    bwr1.write_buffer = (uintptr_t)&bc1;
+    bwr1.write_size = sizeof(bc1);
+    bwr1.read_buffer = (uintptr_t)&br1;
+    bwr1.read_size = sizeof(br1);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr1);
+    EXPECT_EQ(sizeof(bc1), bwr1.write_consumed);
+    EXPECT_EQ(sizeof(br1) - sizeof(br1.pad), bwr1.read_consumed);
+    EXPECT_EQ(BR_NOOP, br1.cmd0);
+    ASSERT_EQ(BR_FROZEN_BINDER, br1.cmd1);
+    EXPECT_FALSE(br1.arg1.is_frozen);
+
+    struct binder_write_read bwr2 = binder_write_read();
+    bwr2.write_buffer = (uintptr_t)&bc2;
+    bwr2.write_size = sizeof(bc2);
+    bwr2.read_buffer = (uintptr_t)&br2;
+    bwr2.read_size = sizeof(br2);
+    binderTestIoctlSuccessOrError(BINDER_WRITE_READ, &bwr2, EAGAIN);
+    binderTestReadEmpty();
+
+    struct binder_write_read bwr3 = binder_write_read();
+    bwr3.write_buffer = (uintptr_t)&bc3;
+    bwr3.write_size = sizeof(bc3);
+    bwr3.read_buffer = (uintptr_t)&br3;
+    bwr3.read_size = sizeof(br3);
+    binderTestIoctl(BINDER_WRITE_READ, &bwr3);
+    EXPECT_EQ(sizeof(bc3), bwr3.write_consumed);
+    EXPECT_EQ(sizeof(br3) - sizeof(br3.pad), bwr3.read_consumed);
+    EXPECT_EQ(BR_CLEAR_FREEZE_NOTIFICATION_DONE, br3.cmd3);
+    EXPECT_EQ(cookie, br3.arg3);
+    binderTestReadEmpty();
+}
+
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
 
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 9b1ba01..391a570 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -27,6 +27,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/result-gmock.h>
 #include <binder/Binder.h>
@@ -39,7 +40,11 @@
 #include <binder/RpcSession.h>
 #include <binder/Status.h>
 #include <binder/unique_fd.h>
+#include <input/BlockingQueue.h>
+#include <processgroup/processgroup.h>
 #include <utils/Flattenable.h>
+#include <utils/SystemClock.h>
+#include "binder/IServiceManagerUnitTestHelper.h"
 
 #include <linux/sched.h>
 #include <sys/epoll.h>
@@ -57,6 +62,7 @@
 using android::base::testing::HasValue;
 using android::binder::Status;
 using android::binder::unique_fd;
+using std::chrono_literals::operator""ms;
 using testing::ExplainMatchResult;
 using testing::Matcher;
 using testing::Not;
@@ -115,6 +121,8 @@
     BINDER_LIB_TEST_NOP_TRANSACTION_WAIT,
     BINDER_LIB_TEST_GETPID,
     BINDER_LIB_TEST_GETUID,
+    BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE,
+    BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS,
     BINDER_LIB_TEST_ECHO_VECTOR,
     BINDER_LIB_TEST_GET_NON_BLOCKING_FD,
     BINDER_LIB_TEST_REJECT_OBJECTS,
@@ -214,6 +222,8 @@
             ASSERT_GT(m_serverpid, 0);
 
             sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
             //printf("%s: pid %d, get service\n", __func__, m_pid);
             LIBBINDER_IGNORE("-Wdeprecated-declarations")
             m_server = sm->getService(binderLibTestServiceName);
@@ -247,11 +257,51 @@
         sp<IBinder> m_server;
 };
 
+class TestFrozenStateChangeCallback : public IBinder::FrozenStateChangeCallback {
+public:
+    BlockingQueue<std::pair<const wp<IBinder>, State>> events;
+
+    virtual void onStateChanged(const wp<IBinder>& who, State state) {
+        events.push(std::make_pair(who, state));
+    }
+
+    void ensureFrozenEventReceived() {
+        auto event = events.popWithTimeout(500ms);
+        ASSERT_TRUE(event.has_value());
+        EXPECT_EQ(State::FROZEN, event->second); // isFrozen should be true
+        EXPECT_EQ(0u, events.size());
+    }
+
+    void ensureUnfrozenEventReceived() {
+        auto event = events.popWithTimeout(500ms);
+        ASSERT_TRUE(event.has_value());
+        EXPECT_EQ(State::UNFROZEN, event->second); // isFrozen should be false
+        EXPECT_EQ(0u, events.size());
+    }
+
+    std::vector<bool> getAllAndClear() {
+        std::vector<bool> results;
+        while (true) {
+            auto event = events.popWithTimeout(0ms);
+            if (!event.has_value()) {
+                break;
+            }
+            results.push_back(event->second == State::FROZEN);
+        }
+        return results;
+    }
+
+    sp<IBinder> binder;
+};
+
 class BinderLibTest : public ::testing::Test {
     public:
         virtual void SetUp() {
             m_server = static_cast<BinderLibTestEnv *>(binder_env)->getServer();
             IPCThreadState::self()->restoreCallingWorkSource(0);
+            sp<IServiceManager> sm = defaultServiceManager();
+            // disable caching during addService.
+            sm->enableAddServiceCache(false);
         }
         virtual void TearDown() {
         }
@@ -291,6 +341,56 @@
             EXPECT_EQ(1, ret);
         }
 
+        bool checkFreezeSupport() {
+            std::string path;
+            if (!CgroupGetAttributePathForTask("FreezerState", getpid(), &path)) {
+                return false;
+            }
+
+            std::ifstream freezer_file(path);
+            // Pass test on devices where the cgroup v2 freezer is not supported
+            if (freezer_file.fail()) {
+                return false;
+            }
+            return IPCThreadState::self()->freeze(getpid(), false, 0) == NO_ERROR;
+        }
+
+        bool checkFreezeAndNotificationSupport() {
+            if (!checkFreezeSupport()) {
+                return false;
+            }
+            return ProcessState::isDriverFeatureEnabled(
+                    ProcessState::DriverFeature::FREEZE_NOTIFICATION);
+        }
+
+        bool getBinderPid(int32_t* pid, sp<IBinder> server) {
+            Parcel data, replypid;
+            if (server->transact(BINDER_LIB_TEST_GETPID, data, &replypid) != NO_ERROR) {
+                ALOGE("BINDER_LIB_TEST_GETPID failed");
+                return false;
+            }
+            *pid = replypid.readInt32();
+            if (*pid <= 0) {
+                ALOGE("pid should be greater than zero");
+                return false;
+            }
+            return true;
+        }
+
+        void freezeProcess(int32_t pid) {
+            EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, true, 1000));
+        }
+
+        void unfreezeProcess(int32_t pid) {
+            EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, false, 0));
+        }
+
+        void removeCallbackAndValidateNoEvent(sp<IBinder> binder,
+                                              sp<TestFrozenStateChangeCallback> callback) {
+            EXPECT_THAT(binder->removeFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+            EXPECT_EQ(0u, callback->events.size());
+        }
+
         sp<IBinder> m_server;
 };
 
@@ -459,14 +559,14 @@
     EXPECT_EQ(NO_ERROR, sm->addService(String16("binderLibTest-manager"), binder));
 }
 
+class LocalRegistrationCallbackImpl : public virtual IServiceManager::LocalRegistrationCallback {
+    void onServiceRegistration(const String16&, const sp<IBinder>&) override {}
+    virtual ~LocalRegistrationCallbackImpl() {}
+};
+
 TEST_F(BinderLibTest, RegisterForNotificationsFailure) {
     auto sm = defaultServiceManager();
-    using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
-    class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
-        void onServiceRegistration(const String16&, const sp<IBinder>&) override {}
-        virtual ~LocalRegistrationCallbackImpl() {}
-    };
-    sp<LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
+    sp<IServiceManager::LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
 
     EXPECT_EQ(BAD_VALUE, sm->registerForNotifications(String16("ValidName"), nullptr));
     EXPECT_EQ(UNKNOWN_ERROR, sm->registerForNotifications(String16("InvalidName!$"), cb));
@@ -474,12 +574,7 @@
 
 TEST_F(BinderLibTest, UnregisterForNotificationsFailure) {
     auto sm = defaultServiceManager();
-    using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
-    class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
-        void onServiceRegistration(const String16&, const sp<IBinder>&) override {}
-        virtual ~LocalRegistrationCallbackImpl() {}
-    };
-    sp<LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
+    sp<IServiceManager::LocalRegistrationCallback> cb = sp<LocalRegistrationCallbackImpl>::make();
 
     EXPECT_EQ(OK, sm->registerForNotifications(String16("ValidName"), cb));
 
@@ -516,29 +611,18 @@
 }
 
 TEST_F(BinderLibTest, Freeze) {
-    Parcel data, reply, replypid;
-    std::ifstream freezer_file("/sys/fs/cgroup/uid_0/cgroup.freeze");
-
-    // Pass test on devices where the cgroup v2 freezer is not supported
-    if (freezer_file.fail()) {
-        GTEST_SKIP();
+    if (!checkFreezeSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support proceess freezing";
         return;
     }
-
+    Parcel data, reply, replypid;
     EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_GETPID, data, &replypid), StatusEq(NO_ERROR));
     int32_t pid = replypid.readInt32();
     for (int i = 0; i < 10; i++) {
         EXPECT_EQ(NO_ERROR, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION_WAIT, data, &reply, TF_ONE_WAY));
     }
 
-    // Pass test on devices where BINDER_FREEZE ioctl is not supported
-    int ret = IPCThreadState::self()->freeze(pid, false, 0);
-    if (ret == -EINVAL) {
-        GTEST_SKIP();
-        return;
-    }
-    EXPECT_EQ(NO_ERROR, ret);
-
+    EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, false, 0));
     EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
 
     // b/268232063 - succeeds ~0.08% of the time
@@ -835,6 +919,199 @@
     EXPECT_THAT(callback->getResult(), StatusEq(NO_ERROR));
 }
 
+TEST_F(BinderLibTest, ReturnErrorIfKernelDoesNotSupportFreezeNotification) {
+    if (ProcessState::isDriverFeatureEnabled(ProcessState::DriverFeature::FREEZE_NOTIFICATION)) {
+        GTEST_SKIP() << "Skipping test for kernels that support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    ASSERT_EQ(nullptr, binder->localBinder());
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(INVALID_OPERATION));
+}
+
+TEST_F(BinderLibTest, FrozenStateChangeNotificatiion) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback->ensureUnfrozenEventReceived();
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    callback->ensureFrozenEventReceived();
+    unfreezeProcess(pid);
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+}
+
+TEST_F(BinderLibTest, AddFrozenCallbackWhenFrozen) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    // Add the callback while the target process is frozen.
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureFrozenEventReceived();
+    unfreezeProcess(pid);
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    // Check that the process hasn't died otherwise there's a risk of freezing
+    // the wrong process.
+    EXPECT_EQ(OK, binder->pingBinder());
+    freezeProcess(pid);
+    unfreezeProcess(pid);
+    // Make sure no callback happens since the listener has been removed.
+    EXPECT_EQ(0u, callback->events.size());
+}
+
+TEST_F(BinderLibTest, NoFrozenNotificationAfterCallbackRemoval) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    // Make sure no callback happens after the listener is removed.
+    freezeProcess(pid);
+    unfreezeProcess(pid);
+    EXPECT_EQ(0u, callback->events.size());
+}
+
+TEST_F(BinderLibTest, MultipleFrozenStateChangeCallbacks) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback1 = sp<TestFrozenStateChangeCallback>::make();
+    sp<TestFrozenStateChangeCallback> callback2 = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback1), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback1->ensureUnfrozenEventReceived();
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback2), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback2->ensureUnfrozenEventReceived();
+
+    freezeProcess(pid);
+    callback1->ensureFrozenEventReceived();
+    callback2->ensureFrozenEventReceived();
+
+    removeCallbackAndValidateNoEvent(binder, callback1);
+    unfreezeProcess(pid);
+    EXPECT_EQ(0u, callback1->events.size());
+    callback2->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback2);
+
+    freezeProcess(pid);
+    EXPECT_EQ(0u, callback2->events.size());
+}
+
+TEST_F(BinderLibTest, RemoveThenAddFrozenStateChangeCallbacks) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    sp<IBinder> binder = addServer();
+    ASSERT_NE(nullptr, binder);
+    int32_t pid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    // Expect current state (unfrozen) to be delivered immediately.
+    callback->ensureUnfrozenEventReceived();
+    removeCallbackAndValidateNoEvent(binder, callback);
+
+    EXPECT_THAT(binder->addFrozenStateChangeCallback(callback), StatusEq(NO_ERROR));
+    callback->ensureUnfrozenEventReceived();
+}
+
+TEST_F(BinderLibTest, CoalesceFreezeCallbacksWhenListenerIsFrozen) {
+    if (!checkFreezeAndNotificationSupport()) {
+        GTEST_SKIP() << "Skipping test for kernels that do not support FREEZE_NOTIFICATION";
+        return;
+    }
+    sp<IBinder> binder = addServer();
+    sp<IBinder> listener = addServer();
+    ASSERT_NE(nullptr, binder);
+    ASSERT_NE(nullptr, listener);
+    int32_t pid, listenerPid;
+    ASSERT_TRUE(getBinderPid(&pid, binder));
+    ASSERT_TRUE(getBinderPid(&listenerPid, listener));
+
+    // Ask the listener process to register for state change callbacks.
+    {
+        Parcel data, reply;
+        data.writeStrongBinder(binder);
+        ASSERT_THAT(listener->transact(BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE, data,
+                                       &reply),
+                    StatusEq(NO_ERROR));
+    }
+    // Freeze the listener process.
+    freezeProcess(listenerPid);
+    createProcessGroup(getuid(), listenerPid);
+    ASSERT_TRUE(SetProcessProfiles(getuid(), listenerPid, {"Frozen"}));
+    // Repeatedly flip the target process between frozen and unfrozen states.
+    for (int i = 0; i < 1000; i++) {
+        usleep(50);
+        unfreezeProcess(pid);
+        usleep(50);
+        freezeProcess(pid);
+    }
+    // Unfreeze the listener process. Now it should receive the frozen state
+    // change notifications.
+    ASSERT_TRUE(SetProcessProfiles(getuid(), listenerPid, {"Unfrozen"}));
+    unfreezeProcess(listenerPid);
+    // Wait for 500ms to give the process enough time to wake up and handle
+    // notifications.
+    usleep(500 * 1000);
+    {
+        std::vector<bool> events;
+        Parcel data, reply;
+        ASSERT_THAT(listener->transact(BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS, data, &reply),
+                    StatusEq(NO_ERROR));
+        reply.readBoolVector(&events);
+        // There should only be one single state change notifications delievered.
+        ASSERT_EQ(1u, events.size());
+        EXPECT_TRUE(events[0]);
+    }
+}
+
 TEST_F(BinderLibTest, PassFile) {
     int ret;
     int pipefd[2];
@@ -1374,6 +1651,7 @@
 
 TEST(ServiceNotifications, Unregister) {
     auto sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
     using LocalRegistrationCallback = IServiceManager::LocalRegistrationCallback;
     class LocalRegistrationCallbackImpl : public virtual LocalRegistrationCallback {
         void onServiceRegistration(const String16 &, const sp<IBinder> &) override {}
@@ -1385,6 +1663,43 @@
     EXPECT_EQ(sm->unregisterForNotifications(String16("RogerRafa"), cb), OK);
 }
 
+// Make sure all IServiceManager APIs will function without an AIDL service
+// manager registered on the device.
+TEST(ServiceManagerNoAidlServer, SanityCheck) {
+    String16 kServiceName("no_services_exist");
+    // This is what clients will see when there is no servicemanager process
+    // that registers itself as context object 0.
+    // Can't use setDefaultServiceManager() here because these test cases run in
+    // the same process and will abort when called twice or before/after
+    // defaultServiceManager().
+    sp<IServiceManager> sm = getServiceManagerShimFromAidlServiceManagerForTests(nullptr);
+    auto status = sm->addService(kServiceName, sp<BBinder>::make());
+    // CppBackendShim returns Status::exceptionCode as the status_t
+    EXPECT_EQ(status, Status::Exception::EX_UNSUPPORTED_OPERATION) << statusToString(status);
+    auto service = sm->checkService(String16("no_services_exist"));
+    EXPECT_TRUE(service == nullptr);
+    auto list = sm->listServices(android::IServiceManager::DUMP_FLAG_PRIORITY_ALL);
+    EXPECT_TRUE(list.isEmpty());
+    bool declared = sm->isDeclared(kServiceName);
+    EXPECT_FALSE(declared);
+    list = sm->getDeclaredInstances(kServiceName);
+    EXPECT_TRUE(list.isEmpty());
+    auto updatable = sm->updatableViaApex(kServiceName);
+    EXPECT_EQ(updatable, std::nullopt);
+    list = sm->getUpdatableNames(kServiceName);
+    EXPECT_TRUE(list.isEmpty());
+    auto conInfo = sm->getConnectionInfo(kServiceName);
+    EXPECT_EQ(conInfo, std::nullopt);
+    auto cb = sp<LocalRegistrationCallbackImpl>::make();
+    status = sm->registerForNotifications(kServiceName, cb);
+    EXPECT_EQ(status, UNKNOWN_ERROR) << statusToString(status);
+    status = sm->unregisterForNotifications(kServiceName, cb);
+    EXPECT_EQ(status, BAD_VALUE) << statusToString(status);
+    auto dbgInfos = sm->getServiceDebugInfo();
+    EXPECT_TRUE(dbgInfos.empty());
+    sm->enableAddServiceCache(true);
+}
+
 TEST_F(BinderLibTest, ThreadPoolAvailableThreads) {
     Parcel data, reply;
     sp<IBinder> server = addServer();
@@ -1440,14 +1755,6 @@
     EXPECT_TRUE(reply.readBool());
 }
 
-size_t epochMillis() {
-    using std::chrono::duration_cast;
-    using std::chrono::milliseconds;
-    using std::chrono::seconds;
-    using std::chrono::system_clock;
-    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
-}
-
 TEST_F(BinderLibTest, HangingServices) {
     Parcel data, reply;
     sp<IBinder> server = addServer();
@@ -1456,7 +1763,7 @@
     data.writeInt32(delay);
     // b/266537959 - must take before taking lock, since countdown is started in the remote
     // process there.
-    size_t epochMsBefore = epochMillis();
+    int64_t timeBefore = uptimeMillis();
     EXPECT_THAT(server->transact(BINDER_LIB_TEST_PROCESS_TEMPORARY_LOCK, data, &reply), NO_ERROR);
     std::vector<std::thread> ts;
     for (size_t i = 0; i < kKernelThreads + 1; i++) {
@@ -1470,10 +1777,10 @@
     for (auto &t : ts) {
         t.join();
     }
-    size_t epochMsAfter = epochMillis();
+    int64_t timeAfter = uptimeMillis();
 
     // deadlock occurred and threads only finished after 1s passed.
-    EXPECT_GE(epochMsAfter, epochMsBefore + delay);
+    EXPECT_GE(timeAfter, timeBefore + delay);
 }
 
 TEST_F(BinderLibTest, BinderProxyCount) {
@@ -1981,6 +2288,26 @@
                 reply->writeInt32(param.sched_priority);
                 return NO_ERROR;
             }
+            case BINDER_LIB_TEST_LISTEN_FOR_FROZEN_STATE_CHANGE: {
+                sp<IBinder> binder = data.readStrongBinder();
+                frozenStateChangeCallback = sp<TestFrozenStateChangeCallback>::make();
+                // Hold an strong pointer to the binder object so it doesn't go
+                // away.
+                frozenStateChangeCallback->binder = binder;
+                int ret = binder->addFrozenStateChangeCallback(frozenStateChangeCallback);
+                if (ret != NO_ERROR) {
+                    return ret;
+                }
+                auto event = frozenStateChangeCallback->events.popWithTimeout(1000ms);
+                if (!event.has_value()) {
+                    return NOT_ENOUGH_DATA;
+                }
+                return NO_ERROR;
+            }
+            case BINDER_LIB_TEST_CONSUME_STATE_CHANGE_EVENTS: {
+                reply->writeBoolVector(frozenStateChangeCallback->getAllAndClear());
+                return NO_ERROR;
+            }
             case BINDER_LIB_TEST_ECHO_VECTOR: {
                 std::vector<uint64_t> vector;
                 auto err = data.readUint64Vector(&vector);
@@ -2067,6 +2394,7 @@
     sp<IBinder> m_callback;
     bool m_exitOnDestroy;
     std::mutex m_blockMutex;
+    sp<TestFrozenStateChangeCallback> frozenStateChangeCallback;
 };
 
 int run_server(int index, int readypipefd, bool usePoll)
@@ -2083,6 +2411,8 @@
 
     status_t ret;
     sp<IServiceManager> sm = defaultServiceManager();
+    sm->enableAddServiceCache(false);
+
     BinderLibTestService* testServicePtr;
     {
         sp<BinderLibTestService> testService = new BinderLibTestService(index);
diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp
index b975fad..f867b42 100644
--- a/libs/binder/tests/binderRecordReplayTest.cpp
+++ b/libs/binder/tests/binderRecordReplayTest.cpp
@@ -99,12 +99,12 @@
     GENERATE_GETTER_SETTER(SingleDataParcelableArray, std::vector<SingleDataParcelable>);
 
     Status setFileDescriptor(unique_fd input) {
-        mFd = std::move(unique_fd(dup(input)));
+        mFd = unique_fd(dup(input));
         return Status::ok();
     }
 
     Status getFileDescriptor(unique_fd* output) {
-        *output = std::move(unique_fd(dup(mFd)));
+        *output = unique_fd(dup(mFd));
         return Status::ok();
     }
     unique_fd mFd;
@@ -117,7 +117,7 @@
     std::vector<uint8_t> buffer(fdStat.st_size);
     auto readResult = android::base::ReadFully(fd, buffer.data(), fdStat.st_size);
     EXPECT_TRUE(readResult != 0);
-    return std::move(buffer);
+    return buffer;
 }
 
 void replayFuzzService(const sp<BpBinder>& binder, const RecordedTransaction& transaction) {
@@ -387,8 +387,8 @@
 
     // When fds are replayed, it will be replaced by /dev/null..reading from it should yield
     // null data
-    recordReplay(&IBinderRecordReplayTest::setFileDescriptor, std::move(unique_fd(dup(saved))),
-                 &IBinderRecordReplayTest::getFileDescriptor, std::move(unique_fd(dup(changed))));
+    recordReplay(&IBinderRecordReplayTest::setFileDescriptor, unique_fd(dup(saved)),
+                 &IBinderRecordReplayTest::getFileDescriptor, unique_fd(dup(changed)));
 }
 
 int main(int argc, char** argv) {
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index cd78e82..170b2ad 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -46,6 +46,13 @@
 #include "binderRpcTestCommon.h"
 #include "binderRpcTestFixture.h"
 
+// TODO need to add IServiceManager.cpp/.h to libbinder_no_kernel
+#ifdef BINDER_WITH_KERNEL_IPC
+#include "android-base/logging.h"
+#include "android/binder_manager.h"
+#include "android/binder_rpc.h"
+#endif // BINDER_WITH_KERNEL_IPC
+
 using namespace std::chrono_literals;
 using namespace std::placeholders;
 using android::binder::borrowed_fd;
@@ -68,6 +75,8 @@
 constexpr char kTrustyIpcDevice[] = "/dev/trusty-ipc-dev0";
 #endif
 
+constexpr char kKnownAidlService[] = "activity";
+
 static std::string WaitStatusToString(int wstatus) {
     if (WIFEXITED(wstatus)) {
         return "exit status " + std::to_string(WEXITSTATUS(wstatus));
@@ -365,26 +374,57 @@
         session->setMaxOutgoingConnections(options.numOutgoingConnections);
         session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
 
+        sockaddr_storage addr{};
+        socklen_t addrLen = 0;
+
         switch (socketType) {
-            case SocketType::PRECONNECTED:
+            case SocketType::PRECONNECTED: {
+                sockaddr_un addr_un{};
+                addr_un.sun_family = AF_UNIX;
+                strcpy(addr_un.sun_path, serverConfig.addr.c_str());
+                addr = *reinterpret_cast<sockaddr_storage*>(&addr_un);
+                addrLen = sizeof(sockaddr_un);
+
                 status = session->setupPreconnectedClient({}, [=]() {
                     return connectTo(UnixSocketAddress(serverConfig.addr.c_str()));
                 });
-                break;
+            } break;
             case SocketType::UNIX_RAW:
-            case SocketType::UNIX:
+            case SocketType::UNIX: {
+                sockaddr_un addr_un{};
+                addr_un.sun_family = AF_UNIX;
+                strcpy(addr_un.sun_path, serverConfig.addr.c_str());
+                addr = *reinterpret_cast<sockaddr_storage*>(&addr_un);
+                addrLen = sizeof(sockaddr_un);
+
                 status = session->setupUnixDomainClient(serverConfig.addr.c_str());
-                break;
+            } break;
             case SocketType::UNIX_BOOTSTRAP:
                 status = session->setupUnixDomainSocketBootstrapClient(
                         unique_fd(dup(bootstrapClientFd.get())));
                 break;
-            case SocketType::VSOCK:
+            case SocketType::VSOCK: {
+                sockaddr_vm addr_vm{
+                        .svm_family = AF_VSOCK,
+                        .svm_port = static_cast<unsigned int>(serverInfo.port),
+                        .svm_cid = VMADDR_CID_LOCAL,
+                };
+                addr = *reinterpret_cast<sockaddr_storage*>(&addr_vm);
+                addrLen = sizeof(sockaddr_vm);
+
                 status = session->setupVsockClient(VMADDR_CID_LOCAL, serverInfo.port);
-                break;
-            case SocketType::INET:
-                status = session->setupInetClient("127.0.0.1", serverInfo.port);
-                break;
+            } break;
+            case SocketType::INET: {
+                const std::string ip_addr = "127.0.0.1";
+                sockaddr_in addr_in{};
+                addr_in.sin_family = AF_INET;
+                addr_in.sin_port = htons(serverInfo.port);
+                inet_aton(ip_addr.c_str(), &addr_in.sin_addr);
+                addr = *reinterpret_cast<sockaddr_storage*>(&addr_in);
+                addrLen = sizeof(sockaddr_in);
+
+                status = session->setupInetClient(ip_addr.c_str(), serverInfo.port);
+            } break;
             case SocketType::TIPC:
                 status = session->setupPreconnectedClient({}, [=]() {
 #ifdef BINDER_RPC_TO_TRUSTY_TEST
@@ -413,7 +453,7 @@
             break;
         }
         LOG_ALWAYS_FATAL_IF(status != OK, "Could not connect: %s", statusToString(status).c_str());
-        ret->sessions.push_back({session, session->getRootObject()});
+        ret->sessions.push_back({session, session->getRootObject(), addr, addrLen});
     }
     return ret;
 }
@@ -423,7 +463,7 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
-    constexpr size_t kNumThreads = 10;
+    constexpr size_t kNumThreads = 5;
 
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
 
@@ -468,11 +508,11 @@
 
     EXPECT_GE(epochMsAfter, epochMsBefore + 2 * sleepMs);
 
-    // Potential flake, but make sure calls are handled in parallel. Due
-    // to past flakes, this only checks that the amount of time taken has
-    // some parallelism. Other tests such as ThreadPoolGreaterThanEqualRequested
-    // check this more exactly.
-    EXPECT_LE(epochMsAfter, epochMsBefore + (numCalls - 1) * sleepMs);
+    // b/272429574, b/365294257
+    // This flakes too much to test. Parallelization is tested
+    // in ThreadPoolGreaterThanEqualRequested and other tests.
+    // Test to make sure calls are handled in parallel.
+    // EXPECT_LE(epochMsAfter, epochMsBefore + (numCalls - 1) * sleepMs);
 }
 
 TEST_P(BinderRpc, ThreadPoolOverSaturated) {
@@ -484,8 +524,7 @@
     constexpr size_t kNumCalls = kNumThreads + 3;
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
 
-    // b/272429574 - below 500ms, the test fails
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 200 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
@@ -499,8 +538,7 @@
     auto proc = createRpcTestSocketServerProcess(
             {.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections});
 
-    // b/272429574 - below 500ms, the test fails
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 200 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadingStressTest) {
@@ -673,6 +711,35 @@
     proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
 }
 
+// TODO(b/392717039): can we move this to universal tests?
+TEST_P(BinderRpc, SendTooLargeVector) {
+    if (GetParam().singleThreaded) {
+        GTEST_SKIP() << "Requires multi-threaded server to test one of the sessions crashing.";
+    }
+
+    auto proc = createRpcTestSocketServerProcess({.numSessions = 2});
+
+    // need a working transaction
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    // see libbinder internal Constants.h
+    const size_t kTooLargeSize = 650 * 1024;
+    const std::vector<uint8_t> kTestValue(kTooLargeSize / sizeof(uint8_t), 42);
+
+    // TODO(b/392717039): Telling a server to allocate too much data currently causes the session to
+    // close since RpcServer treats any transaction error as a failure. We likely want to change
+    // this behavior to be a soft failure, since it isn't hard to keep track of this state.
+    sp<IBinderRpcTest> rootIface2 = interface_cast<IBinderRpcTest>(proc.proc->sessions.at(1).root);
+    std::vector<uint8_t> result;
+    status_t res = rootIface2->repeatBytes(kTestValue, &result).transactionError();
+
+    // TODO(b/392717039): consistent error results always
+    EXPECT_TRUE(res == -ECONNRESET || res == DEAD_OBJECT) << statusToString(res);
+
+    // died, so remove it for checks in destructor of proc
+    proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
+}
+
 TEST_P(BinderRpc, SessionWithIncomingThreadpoolDoesntLeak) {
     if (clientOrServerSingleThreaded()) {
         GTEST_SKIP() << "This test requires multiple threads";
@@ -1127,6 +1194,612 @@
     ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
 }
 
+// TODO need to add IServiceManager.cpp/.h to libbinder_no_kernel
+#ifdef BINDER_WITH_KERNEL_IPC
+
+class BinderRpcAccessor : public BinderRpc {
+    void SetUp() override {
+        if (serverSingleThreaded()) {
+            // This blocks on android::FdTrigger::triggerablePoll when attempting to set
+            // up the client RpcSession
+            GTEST_SKIP() << "Accessors are not supported for single threaded libbinder";
+        }
+        if (rpcSecurity() == RpcSecurity::TLS) {
+            GTEST_SKIP() << "Accessors are not supported with TLS";
+            // ... for now
+        }
+
+        if (socketType() == SocketType::UNIX_BOOTSTRAP) {
+            GTEST_SKIP() << "Accessors do not support UNIX_BOOTSTRAP because no connection "
+                            "information is known";
+        }
+        if (socketType() == SocketType::TIPC) {
+            GTEST_SKIP() << "Accessors do not support TIPC because the socket transport is not "
+                            "known in libbinder";
+        }
+        BinderRpc::SetUp();
+    }
+};
+
+inline void waitForExtraSessionCleanup(const BinderRpcTestProcessSession& proc) {
+    // Need to give the server some time to delete its RpcSession after our last
+    // reference is dropped, closing the connection. Check for up to 1 second,
+    // every 10 ms.
+    for (size_t i = 0; i < 100; i++) {
+        std::vector<int32_t> remoteCounts;
+        EXPECT_OK(proc.rootIface->countBinders(&remoteCounts));
+        // We exect the original binder to still be alive, we just want to wait
+        // for this extra session to be cleaned up.
+        if (remoteCounts.size() == proc.proc->sessions.size()) break;
+        usleep(10000);
+    }
+}
+
+TEST_P(BinderRpcAccessor, InjectAndGetServiceHappyPath) {
+    constexpr size_t kNumThreads = 10;
+    const String16 kInstanceName("super.cool.service/better_than_default");
+
+    auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    auto receipt = addAccessorProvider(
+            {String8(kInstanceName).c_str()}, [&](const String16& name) -> sp<IBinder> {
+                return createAccessor(name,
+                                      [&](const String16& name, sockaddr* outAddr,
+                                          socklen_t addrSize) -> status_t {
+                                          if (outAddr == nullptr ||
+                                              addrSize < proc.proc->sessions[0].addrLen) {
+                                              return BAD_VALUE;
+                                          }
+                                          if (name == kInstanceName) {
+                                              if (proc.proc->sessions[0].addr.ss_family ==
+                                                  AF_UNIX) {
+                                                  sockaddr_un* un = reinterpret_cast<sockaddr_un*>(
+                                                          &proc.proc->sessions[0].addr);
+                                                  ALOGE("inside callback: %s", un->sun_path);
+                                              }
+                                              std::memcpy(outAddr, &proc.proc->sessions[0].addr,
+                                                          proc.proc->sessions[0].addrLen);
+                                              return OK;
+                                          }
+                                          return NAME_NOT_FOUND;
+                                      });
+            });
+
+    EXPECT_FALSE(receipt.expired());
+
+    sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
+    sp<IBinderRpcTest> service = checked_interface_cast<IBinderRpcTest>(binder);
+    EXPECT_NE(service, nullptr);
+
+    sp<IBinder> out;
+    EXPECT_OK(service->repeatBinder(binder, &out));
+    EXPECT_EQ(binder, out);
+
+    out.clear();
+    binder.clear();
+    service.clear();
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+
+    waitForExtraSessionCleanup(proc);
+}
+
+TEST_P(BinderRpcAccessor, InjectNoAccessorProvided) {
+    const String16 kInstanceName("doesnt_matter_nothing_checks");
+
+    bool isProviderDeleted = false;
+
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+
+    sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
+    EXPECT_EQ(binder, nullptr);
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_P(BinderRpcAccessor, InjectDuplicateAccessorProvider) {
+    const String16 kInstanceName("super.cool.service/better_than_default");
+    const String16 kInstanceName2("super.cool.service/better_than_default2");
+
+    auto receipt =
+            addAccessorProvider({String8(kInstanceName).c_str(), String8(kInstanceName2).c_str()},
+                                [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    // reject this because it's associated with an already used instance name
+    auto receipt2 = addAccessorProvider({String8(kInstanceName).c_str()},
+                                        [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_TRUE(receipt2.expired());
+
+    // the first provider should still be usable
+    sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
+    EXPECT_EQ(binder, nullptr);
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_P(BinderRpcAccessor, InjectAccessorProviderNoInstance) {
+    auto receipt = addAccessorProvider({}, [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_TRUE(receipt.expired());
+}
+
+TEST_P(BinderRpcAccessor, InjectNoSockaddrProvided) {
+    constexpr size_t kNumThreads = 10;
+    const String16 kInstanceName("super.cool.service/better_than_default");
+
+    auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    bool isProviderDeleted = false;
+    bool isAccessorDeleted = false;
+
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16& name) -> sp<IBinder> {
+                                           return createAccessor(name,
+                                                                 [&](const String16&, sockaddr*,
+                                                                     socklen_t) -> status_t {
+                                                                     // don't fill in outAddr
+                                                                     return NAME_NOT_FOUND;
+                                                                 });
+                                       });
+
+    EXPECT_FALSE(receipt.expired());
+
+    sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
+    EXPECT_EQ(binder, nullptr);
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+class BinderRpcAccessorNoConnection : public ::testing::Test {};
+
+TEST_F(BinderRpcAccessorNoConnection, listServices) {
+    const String16 kInstanceName("super.cool.service/better_than_default");
+    const String16 kInstanceName2("super.cool.service/better_than_default2");
+
+    auto receipt =
+            addAccessorProvider({String8(kInstanceName).c_str(), String8(kInstanceName2).c_str()},
+                                [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    Vector<String16> list =
+            defaultServiceManager()->listServices(IServiceManager::DUMP_FLAG_PRIORITY_ALL);
+    bool name1 = false;
+    bool name2 = false;
+    for (auto name : list) {
+        if (name == kInstanceName) name1 = true;
+        if (name == kInstanceName2) name2 = true;
+    }
+    EXPECT_TRUE(name1);
+    EXPECT_TRUE(name2);
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_F(BinderRpcAccessorNoConnection, isDeclared) {
+    const String16 kInstanceName("super.cool.service/default");
+    const String16 kInstanceName2("still_counts_as_declared");
+
+    auto receipt =
+            addAccessorProvider({String8(kInstanceName).c_str(), String8(kInstanceName2).c_str()},
+                                [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    EXPECT_TRUE(defaultServiceManager()->isDeclared(kInstanceName));
+    EXPECT_TRUE(defaultServiceManager()->isDeclared(kInstanceName2));
+    EXPECT_FALSE(defaultServiceManager()->isDeclared(String16("doesnt_exist")));
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_F(BinderRpcAccessorNoConnection, getDeclaredInstances) {
+    const String16 kInstanceName("super.cool.service.IFoo/default");
+    const String16 kInstanceName2("super.cool.service.IFoo/extra/default");
+    const String16 kInstanceName3("super.cool.service.IFoo");
+
+    auto receipt =
+            addAccessorProvider({String8(kInstanceName).c_str(), String8(kInstanceName2).c_str(),
+                                 String8(kInstanceName3).c_str()},
+                                [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    Vector<String16> list =
+            defaultServiceManager()->getDeclaredInstances(String16("super.cool.service.IFoo"));
+    // We would prefer ASSERT_EQ here, but we must call removeAccessorProvider
+    EXPECT_EQ(list.size(), 3u);
+    if (list.size() == 3) {
+        bool name1 = false;
+        bool name2 = false;
+        bool name3 = false;
+        for (auto name : list) {
+            if (name == String16("default")) name1 = true;
+            if (name == String16("extra/default")) name2 = true;
+            if (name == String16()) name3 = true;
+        }
+        EXPECT_TRUE(name1) << String8(list[0]);
+        EXPECT_TRUE(name2) << String8(list[1]);
+        EXPECT_TRUE(name3) << String8(list[2]);
+    }
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_F(BinderRpcAccessorNoConnection, getDeclaredWrongInstances) {
+    const String16 kInstanceName("super.cool.service.IFoo");
+
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    Vector<String16> list = defaultServiceManager()->getDeclaredInstances(String16("unknown"));
+    EXPECT_TRUE(list.empty());
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_F(BinderRpcAccessorNoConnection, getDeclaredInstancesSlash) {
+    // This is treated as if there were no '/' and the declared instance is ""
+    const String16 kInstanceName("super.cool.service.IFoo/");
+
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    Vector<String16> list =
+            defaultServiceManager()->getDeclaredInstances(String16("super.cool.service.IFoo"));
+    bool name1 = false;
+    for (auto name : list) {
+        if (name == String16("")) name1 = true;
+    }
+    EXPECT_TRUE(name1);
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+constexpr const char* kARpcInstance = "some.instance.name.IFoo/default";
+const char* kARpcSupportedServices[] = {
+        kARpcInstance,
+};
+const uint32_t kARpcNumSupportedServices = 1;
+
+struct ConnectionInfoData {
+    sockaddr_storage addr;
+    socklen_t len;
+    bool* isDeleted;
+    ~ConnectionInfoData() {
+        if (isDeleted) *isDeleted = true;
+    }
+};
+
+struct AccessorProviderData {
+    sockaddr_storage addr;
+    socklen_t len;
+    bool* isDeleted;
+    ~AccessorProviderData() {
+        if (isDeleted) *isDeleted = true;
+    }
+};
+
+void accessorProviderDataOnDelete(void* data) {
+    delete reinterpret_cast<AccessorProviderData*>(data);
+}
+void infoProviderDataOnDelete(void* data) {
+    delete reinterpret_cast<ConnectionInfoData*>(data);
+}
+
+ABinderRpc_ConnectionInfo* infoProvider(const char* instance, void* cookie) {
+    if (instance == nullptr || cookie == nullptr) return nullptr;
+    ConnectionInfoData* data = reinterpret_cast<ConnectionInfoData*>(cookie);
+    return ABinderRpc_ConnectionInfo_new(reinterpret_cast<const sockaddr*>(&data->addr), data->len);
+}
+
+ABinderRpc_Accessor* getAccessor(const char* instance, void* cookie) {
+    if (instance == nullptr || cookie == nullptr) return nullptr;
+    if (0 != strcmp(instance, kARpcInstance)) return nullptr;
+
+    AccessorProviderData* data = reinterpret_cast<AccessorProviderData*>(cookie);
+
+    ConnectionInfoData* info = new ConnectionInfoData{
+            .addr = data->addr,
+            .len = data->len,
+            .isDeleted = nullptr,
+    };
+
+    return ABinderRpc_Accessor_new(instance, infoProvider, info, infoProviderDataOnDelete);
+}
+
+class BinderARpcNdk : public ::testing::Test {};
+
+TEST_F(BinderARpcNdk, ARpcProviderNewDelete) {
+    bool isDeleted = false;
+
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProviderDeleteOnError) {
+    bool isDeleted = false;
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices, 0, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_EQ(provider, nullptr);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProvideOnErrorNoDeleteCbNoCrash) {
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices, 0, nullptr,
+                                                nullptr);
+
+    ASSERT_EQ(provider, nullptr);
+}
+
+TEST_F(BinderARpcNdk, ARpcProviderDuplicateInstance) {
+    const char* instance = "some.instance.name.IFoo/default";
+    const uint32_t numInstances = 2;
+    const char* instances[numInstances] = {
+            instance,
+            "some.other.instance/default",
+    };
+
+    bool isDeleted = false;
+
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances, numInstances, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    const uint32_t numInstances2 = 1;
+    const char* instances2[numInstances2] = {
+            instance,
+    };
+    bool isDeleted2 = false;
+    AccessorProviderData* data2 = new AccessorProviderData{{}, 0, &isDeleted2};
+    ABinderRpc_AccessorProvider* provider2 =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances2, numInstances2, data2,
+                                                accessorProviderDataOnDelete);
+
+    EXPECT_EQ(provider2, nullptr);
+    // If it fails to be registered, the data is still cleaned up with
+    // accessorProviderDataOnDelete
+    EXPECT_TRUE(isDeleted2);
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProviderRegisterNoInstance) {
+    const uint32_t numInstances = 0;
+    const char* instances[numInstances] = {};
+
+    bool isDeleted = false;
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances, numInstances, data,
+                                                accessorProviderDataOnDelete);
+    ASSERT_EQ(provider, nullptr);
+}
+
+TEST_F(BinderARpcNdk, ARpcAccessorNewDelete) {
+    bool isDeleted = false;
+
+    ConnectionInfoData* data = new ConnectionInfoData{{}, 0, &isDeleted};
+
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("gshoe_service", infoProvider, data, infoProviderDataOnDelete);
+    ASSERT_NE(accessor, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    ABinderRpc_Accessor_delete(accessor);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcConnectionInfoNewDelete) {
+    sockaddr_vm addr{
+            .svm_family = AF_VSOCK,
+            .svm_port = VMADDR_PORT_ANY,
+            .svm_cid = VMADDR_CID_ANY,
+    };
+
+    ABinderRpc_ConnectionInfo* info =
+            ABinderRpc_ConnectionInfo_new(reinterpret_cast<sockaddr*>(&addr), sizeof(sockaddr_vm));
+    EXPECT_NE(info, nullptr);
+
+    ABinderRpc_ConnectionInfo_delete(info);
+}
+
+TEST_F(BinderARpcNdk, ARpcAsFromBinderAsBinder) {
+    bool isDeleted = false;
+
+    ConnectionInfoData* data = new ConnectionInfoData{{}, 0, &isDeleted};
+
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("gshoe_service", infoProvider, data, infoProviderDataOnDelete);
+    ASSERT_NE(accessor, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    {
+        ndk::SpAIBinder binder = ndk::SpAIBinder(ABinderRpc_Accessor_asBinder(accessor));
+        EXPECT_NE(binder.get(), nullptr);
+
+        ABinderRpc_Accessor* accessor2 =
+                ABinderRpc_Accessor_fromBinder("wrong_service_name", binder.get());
+        // The API checks for the expected service name that is associated with
+        // the accessor!
+        EXPECT_EQ(accessor2, nullptr);
+
+        accessor2 = ABinderRpc_Accessor_fromBinder("gshoe_service", binder.get());
+        EXPECT_NE(accessor2, nullptr);
+
+        // this is a new ABinderRpc_Accessor object that wraps the underlying
+        // libbinder object.
+        EXPECT_NE(accessor, accessor2);
+
+        ndk::SpAIBinder binder2 = ndk::SpAIBinder(ABinderRpc_Accessor_asBinder(accessor2));
+        EXPECT_EQ(binder.get(), binder2.get());
+
+        ABinderRpc_Accessor_delete(accessor2);
+    }
+
+    EXPECT_FALSE(isDeleted);
+    ABinderRpc_Accessor_delete(accessor);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcRequireProviderOnDeleteCallback) {
+    EXPECT_EQ(nullptr,
+              ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                  kARpcNumSupportedServices,
+                                                  reinterpret_cast<void*>(1), nullptr));
+}
+
+TEST_F(BinderARpcNdk, ARpcRequireInfoOnDeleteCallback) {
+    EXPECT_EQ(nullptr,
+              ABinderRpc_Accessor_new("the_best_service_name", infoProvider,
+                                      reinterpret_cast<void*>(1), nullptr));
+}
+
+TEST_F(BinderARpcNdk, ARpcNoDataNoProviderOnDeleteCallback) {
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, nullptr, nullptr);
+    ASSERT_NE(nullptr, provider);
+    ABinderRpc_unregisterAccessorProvider(provider);
+}
+
+TEST_F(BinderARpcNdk, ARpcNoDataNoInfoOnDeleteCallback) {
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("the_best_service_name", infoProvider, nullptr, nullptr);
+    ASSERT_NE(nullptr, accessor);
+    ABinderRpc_Accessor_delete(accessor);
+}
+
+TEST_F(BinderARpcNdk, ARpcNullArgs_ConnectionInfo_new) {
+    sockaddr_storage addr;
+    EXPECT_EQ(nullptr, ABinderRpc_ConnectionInfo_new(reinterpret_cast<const sockaddr*>(&addr), 0));
+}
+
+TEST_F(BinderARpcNdk, ARpcDelegateAccessorWrongInstance) {
+    AccessorProviderData* data = new AccessorProviderData();
+    ABinderRpc_Accessor* accessor = getAccessor(kARpcInstance, data);
+    ASSERT_NE(accessor, nullptr);
+    AIBinder* localAccessorBinder = ABinderRpc_Accessor_asBinder(accessor);
+    EXPECT_NE(localAccessorBinder, nullptr);
+
+    AIBinder* delegatorBinder = nullptr;
+    binder_status_t status =
+            ABinderRpc_Accessor_delegateAccessor("bar", localAccessorBinder, &delegatorBinder);
+    EXPECT_EQ(status, NAME_NOT_FOUND);
+
+    AIBinder_decStrong(localAccessorBinder);
+    ABinderRpc_Accessor_delete(accessor);
+    delete data;
+}
+
+TEST_F(BinderARpcNdk, ARpcDelegateNonAccessor) {
+    auto service = defaultServiceManager()->checkService(String16(kKnownAidlService));
+    ASSERT_NE(nullptr, service);
+    ndk::SpAIBinder binder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(service));
+
+    AIBinder* delegatorBinder = nullptr;
+    binder_status_t status =
+            ABinderRpc_Accessor_delegateAccessor("bar", binder.get(), &delegatorBinder);
+
+    EXPECT_EQ(status, BAD_TYPE);
+}
+
+inline void getServiceTest(BinderRpcTestProcessSession& proc,
+                           ABinderRpc_AccessorProvider_getAccessorCallback getAccessor) {
+    constexpr size_t kNumThreads = 10;
+    bool isDeleted = false;
+
+    AccessorProviderData* data =
+            new AccessorProviderData{proc.proc->sessions[0].addr, proc.proc->sessions[0].addrLen,
+                                     &isDeleted};
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, data,
+                                                accessorProviderDataOnDelete);
+    EXPECT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    {
+        ndk::SpAIBinder binder = ndk::SpAIBinder(AServiceManager_checkService(kARpcInstance));
+        ASSERT_NE(binder.get(), nullptr);
+        EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
+    }
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+    EXPECT_TRUE(isDeleted);
+
+    waitForExtraSessionCleanup(proc);
+}
+
+TEST_P(BinderRpcAccessor, ARpcGetService) {
+    constexpr size_t kNumThreads = 10;
+    auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    getServiceTest(proc, getAccessor);
+}
+
+// Create accessors and wrap each of the accessors in a delegator
+ABinderRpc_Accessor* getDelegatedAccessor(const char* instance, void* cookie) {
+    ABinderRpc_Accessor* accessor = getAccessor(instance, cookie);
+    AIBinder* accessorBinder = ABinderRpc_Accessor_asBinder(accessor);
+    // Once we have a handle to the AIBinder which holds a reference to the
+    // underlying accessor IBinder, we can get rid of the ABinderRpc_Accessor
+    ABinderRpc_Accessor_delete(accessor);
+
+    AIBinder* delegatorBinder = nullptr;
+    binder_status_t status =
+            ABinderRpc_Accessor_delegateAccessor(instance, accessorBinder, &delegatorBinder);
+    // No longer need this AIBinder. The delegator has a reference to the
+    // underlying IBinder on success, and on failure we are done here.
+    AIBinder_decStrong(accessorBinder);
+    if (status != OK || delegatorBinder == nullptr) {
+        ALOGE("Unexpected behavior. Status: %s, delegator ptr: %p", statusToString(status).c_str(),
+              delegatorBinder);
+        return nullptr;
+    }
+
+    return ABinderRpc_Accessor_fromBinder(instance, delegatorBinder);
+}
+
+TEST_P(BinderRpcAccessor, ARpcGetServiceWithDelegator) {
+    constexpr size_t kNumThreads = 10;
+    auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    getServiceTest(proc, getDelegatedAccessor);
+}
+
+#endif // BINDER_WITH_KERNEL_IPC
+
 #ifdef BINDER_RPC_TO_TRUSTY_TEST
 
 static std::vector<BinderRpc::ParamType> getTrustyBinderRpcParams() {
@@ -1315,6 +1988,11 @@
 INSTANTIATE_TEST_SUITE_P(PerSocket, BinderRpc, ::testing::ValuesIn(getBinderRpcParams()),
                          BinderRpc::PrintParamInfo);
 
+#ifdef BINDER_WITH_KERNEL_IPC
+INSTANTIATE_TEST_SUITE_P(PerSocket, BinderRpcAccessor, ::testing::ValuesIn(getBinderRpcParams()),
+                         BinderRpc::PrintParamInfo);
+#endif // BINDER_WITH_KERNEL_IPC
+
 class BinderRpcServerRootObject
       : public ::testing::TestWithParam<std::tuple<bool, bool, RpcSecurity>> {};
 
@@ -1384,8 +2062,8 @@
     sp<IServiceManager> sm = defaultServiceManager();
     ASSERT_NE(nullptr, sm);
     // Any Java service with non-empty getInterfaceDescriptor() would do.
-    // Let's pick batteryproperties.
-    auto binder = sm->checkService(String16("batteryproperties"));
+    // Let's pick activity.
+    auto binder = sm->checkService(String16(kKnownAidlService));
     ASSERT_NE(nullptr, binder);
     auto descriptor = binder->getInterfaceDescriptor();
     ASSERT_GE(descriptor.size(), 0u);
@@ -2093,7 +2771,9 @@
 
 int main(int argc, char** argv) {
     ::testing::InitGoogleTest(&argc, argv);
+#ifndef __ANDROID__
     __android_log_set_logger(__android_log_stderr_logger);
+#endif
 
     return RUN_ALL_TESTS();
 }
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index dc22647..6e00246 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -348,6 +348,10 @@
         *out = binder;
         return Status::ok();
     }
+    Status repeatBytes(const std::vector<uint8_t>& bytes, std::vector<uint8_t>* out) override {
+        *out = bytes;
+        return Status::ok();
+    }
     static sp<IBinder> mHeldBinder;
     Status holdBinder(const sp<IBinder>& binder) override {
         mHeldBinder = binder;
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
index 2c9646b..c8a8acc 100644
--- a/libs/binder/tests/binderRpcTestFixture.h
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -35,6 +35,12 @@
     struct SessionInfo {
         sp<RpcSession> session;
         sp<IBinder> root;
+// Trusty defines its own socket APIs in trusty_ipc.h but doesn't include
+// sockaddr types.
+#ifndef __TRUSTY__
+        sockaddr_storage addr;
+        socklen_t addrLen;
+#endif
     };
 
     // client session objects associated with other process
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index aef9464..0084b9a 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -100,7 +100,9 @@
 };
 
 int main(int argc, char* argv[]) {
+#ifndef __ANDROID__
     __android_log_set_logger(__android_log_stderr_logger);
+#endif
 
     LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
     unique_fd writeEnd(atoi(argv[1]));
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index f480780..d227e6e 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -209,6 +209,18 @@
     EXPECT_EQ(0, MyBinderRpcSession::gNum);
 }
 
+TEST_P(BinderRpc, SendLargeVector) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    // see libbinder internal Constants.h
+    const size_t kLargeSize = 550 * 1024;
+    const std::vector<uint8_t> kTestValue(kLargeSize / sizeof(uint8_t), 42);
+
+    std::vector<uint8_t> result;
+    EXPECT_OK(proc.rootIface->repeatBytes(kTestValue, &result));
+    EXPECT_EQ(result, kTestValue);
+}
+
 TEST_P(BinderRpc, RepeatTheirBinder) {
     auto proc = createRpcTestSocketServerProcess({});
 
@@ -301,7 +313,8 @@
 
     auto proc = createRpcTestSocketServerProcess({});
 
-    sp<IBinder> someRealBinder = IInterface::asBinder(defaultServiceManager());
+    sp<IBinder> someRealBinder = defaultServiceManager()->getService(String16("activity"));
+    ASSERT_NE(someRealBinder, nullptr);
     sp<IBinder> outBinder;
     EXPECT_EQ(INVALID_OPERATION,
               proc.rootIface->repeatBinder(someRealBinder, &outBinder).transactionError());
@@ -323,6 +336,22 @@
 
 // END TESTS FOR LIMITATIONS OF SOCKET BINDER
 
+class TestFrozenStateChangeCallback : public IBinder::FrozenStateChangeCallback {
+public:
+    virtual void onStateChanged(const wp<IBinder>&, State) {}
+};
+
+TEST_P(BinderRpc, RpcBinderShouldFailOnFrozenStateCallbacks) {
+    auto proc = createRpcTestSocketServerProcess({});
+
+    sp<IBinder> a;
+    sp<TestFrozenStateChangeCallback> callback = sp<TestFrozenStateChangeCallback>::make();
+    EXPECT_OK(proc.rootIface->alwaysGiveMeTheSameBinder(&a));
+    EXPECT_DEATH_IF_SUPPORTED(
+            { std::ignore = a->addFrozenStateChangeCallback(callback); },
+            "addFrozenStateChangeCallback\\(\\) is not supported for RPC Binder.");
+}
+
 TEST_P(BinderRpc, RepeatRootObject) {
     auto proc = createRpcTestSocketServerProcess({});
 
@@ -481,9 +510,9 @@
                 // same thread, everything should have happened in a nested call. Otherwise,
                 // the callback will be processed on another thread.
                 if (callIsOneway || callbackIsOneway || delayed) {
-                    using std::literals::chrono_literals::operator""s;
+                    using std::literals::chrono_literals::operator""ms;
                     RpcMutexUniqueLock _l(cb->mMutex);
-                    cb->mCv.wait_for(_l, 1s, [&] { return !cb->mValues.empty(); });
+                    cb->mCv.wait_for(_l, 1500ms, [&] { return !cb->mValues.empty(); });
                 }
 
                 EXPECT_EQ(cb->mValues.size(), 1UL)
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index 0aa678d..45b2103 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -40,6 +40,8 @@
 #include <sys/eventfd.h>
 #include <sys/prctl.h>
 
+#include <gmock/gmock.h>
+
 using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
 using android::binder::unique_fd;
 
@@ -222,6 +224,7 @@
         SetDeathToken = IBinder::FIRST_CALL_TRANSACTION,
         ReturnsNoMemory,
         LogicalNot,
+        LogicalNotVector,
         ModifyEnum,
         IncrementFlattenable,
         IncrementLightFlattenable,
@@ -249,6 +252,7 @@
 
     // These are ordered according to their corresponding methods in SafeInterface::ParcelHandler
     virtual status_t logicalNot(bool a, bool* notA) const = 0;
+    virtual status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const = 0;
     virtual status_t modifyEnum(TestEnum a, TestEnum* b) const = 0;
     virtual status_t increment(const TestFlattenable& a, TestFlattenable* aPlusOne) const = 0;
     virtual status_t increment(const TestLightFlattenable& a,
@@ -288,7 +292,14 @@
     }
     status_t logicalNot(bool a, bool* notA) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
-        return callRemote<decltype(&ISafeInterfaceTest::logicalNot)>(Tag::LogicalNot, a, notA);
+        using Signature = status_t (ISafeInterfaceTest::*)(bool, bool*) const;
+        return callRemote<Signature>(Tag::LogicalNot, a, notA);
+    }
+    status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const override {
+        ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
+        using Signature = status_t (ISafeInterfaceTest::*)(const std::vector<bool>&,
+                                                           std::vector<bool>*) const;
+        return callRemote<Signature>(Tag::LogicalNotVector, a, notA);
     }
     status_t modifyEnum(TestEnum a, TestEnum* b) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
@@ -406,6 +417,14 @@
         *notA = !a;
         return NO_ERROR;
     }
+    status_t logicalNot(const std::vector<bool>& a, std::vector<bool>* notA) const override {
+        ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
+        notA->clear();
+        for (bool value : a) {
+            notA->push_back(!value);
+        }
+        return NO_ERROR;
+    }
     status_t modifyEnum(TestEnum a, TestEnum* b) const override {
         ALOG(LOG_INFO, getLogTag(), "%s", __PRETTY_FUNCTION__);
         *b = (a == TestEnum::INITIAL) ? TestEnum::FINAL : TestEnum::INVALID;
@@ -513,7 +532,13 @@
                 return callLocal(data, reply, &ISafeInterfaceTest::returnsNoMemory);
             }
             case ISafeInterfaceTest::Tag::LogicalNot: {
-                return callLocal(data, reply, &ISafeInterfaceTest::logicalNot);
+                using Signature = status_t (ISafeInterfaceTest::*)(bool a, bool* notA) const;
+                return callLocal<Signature>(data, reply, &ISafeInterfaceTest::logicalNot);
+            }
+            case ISafeInterfaceTest::Tag::LogicalNotVector: {
+                using Signature = status_t (ISafeInterfaceTest::*)(const std::vector<bool>& a,
+                                                                   std::vector<bool>* notA) const;
+                return callLocal<Signature>(data, reply, &ISafeInterfaceTest::logicalNot);
             }
             case ISafeInterfaceTest::Tag::ModifyEnum: {
                 return callLocal(data, reply, &ISafeInterfaceTest::modifyEnum);
@@ -639,6 +664,15 @@
     ASSERT_EQ(!b, notB);
 }
 
+TEST_F(SafeInterfaceTest, TestLogicalNotVector) {
+    const std::vector<bool> a = {true, false, true};
+    std::vector<bool> notA;
+    status_t result = mSafeInterfaceTest->logicalNot(a, &notA);
+    ASSERT_EQ(NO_ERROR, result);
+    std::vector<bool> expected = {false, true, false};
+    ASSERT_THAT(notA, testing::ContainerEq(expected));
+}
+
 TEST_F(SafeInterfaceTest, TestModifyEnum) {
     const TestEnum a = TestEnum::INITIAL;
     TestEnum b = TestEnum::INVALID;
@@ -755,7 +789,7 @@
         std::optional<int32_t> waitForCallback() {
             std::unique_lock<decltype(mMutex)> lock(mMutex);
             bool success =
-                    mCondition.wait_for(lock, 100ms, [&]() { return static_cast<bool>(mValue); });
+                    mCondition.wait_for(lock, 1000ms, [&]() { return static_cast<bool>(mValue); });
             return success ? mValue : std::nullopt;
         }
 
@@ -824,7 +858,13 @@
     ASSERT_EQ(b + 1, bPlusOne);
 }
 
-extern "C" int main(int argc, char **argv) {
+} // namespace tests
+} // namespace android
+
+int main(int argc, char** argv) {
+    using namespace android;
+    using namespace android::tests;
+
     testing::InitGoogleTest(&argc, argv);
 
     if (fork() == 0) {
@@ -841,6 +881,3 @@
 
     return RUN_ALL_TESTS();
 }
-
-} // namespace tests
-} // namespace android
diff --git a/libs/binder/tests/binderStabilityIntegrationTest.cpp b/libs/binder/tests/binderStabilityIntegrationTest.cpp
new file mode 100644
index 0000000..cbc4180
--- /dev/null
+++ b/libs/binder/tests/binderStabilityIntegrationTest.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Stability.h>
+#include <gtest/gtest.h>
+#include <procpartition/procpartition.h>
+
+using namespace android;
+using android::internal::Stability; // for testing only!
+using android::procpartition::getPartition;
+using android::procpartition::Partition;
+
+class BinderStabilityIntegrationTest : public testing::Test,
+                                       public ::testing::WithParamInterface<String16> {
+public:
+    virtual ~BinderStabilityIntegrationTest() {}
+};
+
+TEST_P(BinderStabilityIntegrationTest, ExpectedStabilityForItsPartition) {
+    const String16& serviceName = GetParam();
+
+    sp<IBinder> binder = defaultServiceManager()->checkService(serviceName);
+    if (!binder) GTEST_SKIP() << "Could not get service, may have gone away.";
+
+    pid_t pid;
+    status_t res = binder->getDebugPid(&pid);
+    if (res != OK) {
+        GTEST_SKIP() << "Could not talk to service to get PID, res: " << statusToString(res);
+    }
+
+    Partition partition = getPartition(pid);
+
+    Stability::Level level = Stability::Level::UNDECLARED;
+    switch (partition) {
+        case Partition::PRODUCT:
+        case Partition::SYSTEM:
+        case Partition::SYSTEM_EXT:
+            level = Stability::Level::SYSTEM;
+            break;
+        case Partition::VENDOR:
+        case Partition::ODM:
+            level = Stability::Level::VENDOR;
+            break;
+        case Partition::UNKNOWN:
+            GTEST_SKIP() << "Not sure of partition of process.";
+            return;
+        default:
+            ADD_FAILURE() << "Unrecognized partition for service: " << partition;
+            return;
+    }
+
+    ASSERT_TRUE(Stability::check(Stability::getRepr(binder.get()), level))
+            << "Binder hosted on partition " << partition
+            << " should have corresponding stability set.";
+}
+
+std::string PrintTestParam(
+        const testing::TestParamInfo<BinderStabilityIntegrationTest::ParamType>& info) {
+    std::string name = String8(info.param).c_str();
+    for (size_t i = 0; i < name.size(); i++) {
+        bool alnum = false;
+        alnum |= (name[i] >= 'a' && name[i] <= 'z');
+        alnum |= (name[i] >= 'A' && name[i] <= 'Z');
+        alnum |= (name[i] >= '0' && name[i] <= '9');
+        alnum |= (name[i] == '_');
+        if (!alnum) name[i] = '_';
+    }
+
+    // index for uniqueness
+    return std::to_string(info.index) + "__" + name;
+}
+
+INSTANTIATE_TEST_CASE_P(RegisteredServices, BinderStabilityIntegrationTest,
+                        ::testing::ValuesIn(defaultServiceManager()->listServices()),
+                        PrintTestParam);
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 6301c74..ca70b66 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -56,7 +56,7 @@
         });
         auto elapsedMs = millisSince(start);
         EXPECT_GE(elapsedMs, 1000);
-        EXPECT_LT(elapsedMs, 2000);
+        EXPECT_LT(elapsedMs, 3000); // b/377571547: higher to reduce flake
 
         ASSERT_TRUE(result.has_value());
         EXPECT_EQ(std::nullopt, result->exitCode);
@@ -65,7 +65,7 @@
 
     // ~CommandResult() called, child process is killed.
     // Assert that the second sleep does not finish.
-    EXPECT_LT(millisSince(start), 2000);
+    EXPECT_LT(millisSince(start), 3000);
 }
 
 TEST(UtilsHost, ExecuteLongRunning2) {
@@ -89,8 +89,8 @@
     }
 
     // ~CommandResult() called, child process is killed.
-    // Assert that the second sleep does not finish.
-    EXPECT_LT(millisSince(start), 6000);
+    // Assert that the last sleep does not finish.
+    EXPECT_LT(millisSince(start), 8000);
 }
 
 TEST(UtilsHost, KillWithSigKill) {
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index fbab8f0..cac054e 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -39,6 +39,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        triage_assignee: "smoreland@google.com",
         use_for_presubmit: true,
     },
 
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 5c280f4..20b6b44 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -115,6 +115,14 @@
         p.setDataPosition(pos);
         FUZZ_LOG() << "setDataPosition done";
     },
+    [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null");
+        // TODO: allow all read and write operations
+        (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size());
+        FUZZ_LOG() << "setData done";
+    },
     PARCEL_READ_NO_STATUS(size_t, allowFds),
     PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
     PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
@@ -310,8 +318,6 @@
     PARCEL_READ_NO_STATUS(int, readParcelFileDescriptor),
     PARCEL_READ_WITH_STATUS(unique_fd, readUniqueFileDescriptor),
 
-    PARCEL_READ_WITH_STATUS(std::unique_ptr<std::vector<unique_fd>>,
-            readUniqueFileDescriptorVector),
     PARCEL_READ_WITH_STATUS(std::optional<std::vector<unique_fd>>, readUniqueFileDescriptorVector),
     PARCEL_READ_WITH_STATUS(std::vector<unique_fd>, readUniqueFileDescriptorVector),
 
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 3a1471e..e3a3371 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -49,7 +49,8 @@
     return STATUS_UNKNOWN_TRANSACTION;
 }
 
-static AIBinder_Class* g_class = ::ndk::ICInterface::defineClass("ISomeInterface", onTransact);
+static AIBinder_Class* g_class =
+        ::ndk::ICInterface::defineClass("ISomeInterface", onTransact, nullptr, 0);
 
 class BpSomeInterface : public ::ndk::BpCInterface<ISomeInterface> {
 public:
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index 5b1e9ea..a57d07f 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -46,7 +46,18 @@
     (void)options;
 
     std::vector<uint8_t> input = provider.ConsumeRemainingBytes<uint8_t>();
-    p->setData(input.data(), input.size());
+
+    if (input.size() % 4 != 0) {
+        input.resize(input.size() + (sizeof(uint32_t) - input.size() % sizeof(uint32_t)));
+    }
+    CHECK_EQ(0, input.size() % 4);
+
+    p->setDataCapacity(input.size());
+    for (size_t i = 0; i < input.size(); i += 4) {
+        p->writeInt32(*((int32_t*)(input.data() + i)));
+    }
+
+    CHECK_EQ(0, memcmp(input.data(), p->data(), p->dataSize()));
 }
 static void fillRandomParcel(NdkParcelAdapter* p, FuzzedDataProvider&& provider,
                              RandomParcelOptions* options) {
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
index fd9777a..0ed8a55 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
@@ -55,7 +55,7 @@
         offset += CHAR_BIT;
     }
 
-    return std::move(reverseData);
+    return reverseData;
 }
 
 template <typename T>
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
index 690c39a..4008c56 100644
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
@@ -59,6 +59,11 @@
         darwin: {
             enabled: false,
         },
+        host: {
+            data_libs: [
+                "libc++",
+            ],
+        },
     },
     test_suites: ["general-tests"],
 }
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
index 5d68fe1..b623c5f 100755
--- a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
@@ -39,6 +39,7 @@
     else
         echo -e "${color_failed}Failed: Unable to find successful fuzzing output from test_service_fuzzer_should_crash"
         echo "${color_reset}"
+        cat "$FUZZER_OUT"
         exit 1
     fi
 done
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 157ab3c..ba9e457 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -42,6 +42,10 @@
 
 void trace_int(uint64_t, const char*, int32_t) {}
 
+uint64_t get_trace_enabled_tags() {
+    return 0;
+}
+
 uint64_t GetThreadId() {
     return 0;
 }
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 17919c2..0580046 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -151,8 +151,10 @@
 
 int RpcServerTrusty::handleMessageInternal(void* ctx) {
     auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
-    LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
-                        "bad state: message received on uninitialized channel");
+    if (channelContext == nullptr) {
+        LOG_RPC_DETAIL("bad state: message received on uninitialized channel");
+        return ERR_BAD_STATE;
+    }
 
     auto& session = channelContext->session;
     auto& connection = channelContext->connection;
diff --git a/libs/binder/trusty/RpcTransportTipcTrusty.cpp b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
index c74ba0a..65ad896 100644
--- a/libs/binder/trusty/RpcTransportTipcTrusty.cpp
+++ b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
@@ -47,6 +47,71 @@
         return mHaveMessage ? OK : WOULD_BLOCK;
     }
 
+    void moveMsgStart(ipc_msg_t* msg, size_t msg_size, size_t offset) {
+        LOG_ALWAYS_FATAL_IF(offset > msg_size, "tried to move message past its end %zd>%zd", offset,
+                            msg_size);
+        while (true) {
+            if (offset == 0) {
+                break;
+            }
+            if (offset >= msg->iov[0].iov_len) {
+                // Move to the next iov, this one was sent already
+                offset -= msg->iov[0].iov_len;
+                msg->iov++;
+                msg->num_iov -= 1;
+            } else {
+                // We need to move the base of the current iov
+                msg->iov[0].iov_len -= offset;
+                msg->iov[0].iov_base = static_cast<char*>(msg->iov[0].iov_base) + offset;
+                offset = 0;
+            }
+        }
+        // We only send handles on the first message. This can be changed in the future if we want
+        // to send more handles than the maximum per message limit (which would require sending
+        // multiple messages). The current code makes sure that we send less handles than the
+        // maximum trusty allows.
+        msg->num_handles = 0;
+    }
+
+    status_t sendTrustyMsg(ipc_msg_t* msg, size_t msg_size) {
+        do {
+            ssize_t rc = send_msg(mSocket.fd.get(), msg);
+            if (rc == ERR_NOT_ENOUGH_BUFFER) {
+                // Peer is blocked, wait until it unblocks.
+                // TODO: when tipc supports a send-unblocked handler,
+                // save the message here in a queue and retry it asynchronously
+                // when the handler gets called by the library
+                uevent uevt;
+                do {
+                    rc = ::wait(mSocket.fd.get(), &uevt, INFINITE_TIME);
+                    if (rc < 0) {
+                        return statusFromTrusty(rc);
+                    }
+                    if (uevt.event & IPC_HANDLE_POLL_HUP) {
+                        return DEAD_OBJECT;
+                    }
+                } while (!(uevt.event & IPC_HANDLE_POLL_SEND_UNBLOCKED));
+
+                // Retry the send, it should go through this time because
+                // sending is now unblocked
+                rc = send_msg(mSocket.fd.get(), msg);
+            }
+            if (rc < 0) {
+                return statusFromTrusty(rc);
+            }
+            size_t sent_bytes = static_cast<size_t>(rc);
+            if (sent_bytes < msg_size) {
+                moveMsgStart(msg, msg_size, static_cast<size_t>(sent_bytes));
+                msg_size -= sent_bytes;
+            } else {
+                LOG_ALWAYS_FATAL_IF(static_cast<size_t>(rc) != msg_size,
+                                    "Sent the wrong number of bytes %zd!=%zu", rc, msg_size);
+                break;
+            }
+        } while (true);
+        return OK;
+    }
+
     status_t interruptableWriteFully(
             FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
             const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
@@ -86,34 +151,7 @@
             msg.handles = msgHandles;
         }
 
-        ssize_t rc = send_msg(mSocket.fd.get(), &msg);
-        if (rc == ERR_NOT_ENOUGH_BUFFER) {
-            // Peer is blocked, wait until it unblocks.
-            // TODO: when tipc supports a send-unblocked handler,
-            // save the message here in a queue and retry it asynchronously
-            // when the handler gets called by the library
-            uevent uevt;
-            do {
-                rc = ::wait(mSocket.fd.get(), &uevt, INFINITE_TIME);
-                if (rc < 0) {
-                    return statusFromTrusty(rc);
-                }
-                if (uevt.event & IPC_HANDLE_POLL_HUP) {
-                    return DEAD_OBJECT;
-                }
-            } while (!(uevt.event & IPC_HANDLE_POLL_SEND_UNBLOCKED));
-
-            // Retry the send, it should go through this time because
-            // sending is now unblocked
-            rc = send_msg(mSocket.fd.get(), &msg);
-        }
-        if (rc < 0) {
-            return statusFromTrusty(rc);
-        }
-        LOG_ALWAYS_FATAL_IF(static_cast<size_t>(rc) != size,
-                            "Sent the wrong number of bytes %zd!=%zu", rc, size);
-
-        return OK;
+        return sendTrustyMsg(&msg, size);
     }
 
     status_t interruptableReadFully(
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
index 6e20b8a..da0f2ed 100644
--- a/libs/binder/trusty/binderRpcTest/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -1,6 +1,6 @@
 {
     "uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
     "app_name": "binderRpcTest",
-    "min_heap": 262144,
+    "min_heap": 4194304,
     "min_stack": 20480
 }
diff --git a/libs/binder/trusty/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
index d2a1fc0..55ff49c 100644
--- a/libs/binder/trusty/binderRpcTest/service/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -1,7 +1,7 @@
 {
     "uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
     "app_name": "binderRpcTestService",
-    "min_heap": 65536,
+    "min_heap": 4194304,
     "min_stack": 20480,
     "mgmt_flags": {
         "restart_on_exit": true,
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index fe44ea5..583ad01 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -42,7 +42,7 @@
     // equivalent.
     struct PortAcl {
         uint32_t flags;
-        std::vector<const uuid> uuids;
+        std::vector<uuid> uuids;
         const void* extraData;
     };
 
diff --git a/libs/binder/trusty/ndk/include/android/llndk-versioning.h b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
deleted file mode 100644
index 3ae3d8f..0000000
--- a/libs/binder/trusty/ndk/include/android/llndk-versioning.h
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 5e38ad0..dd9d4d1 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -64,14 +64,24 @@
 MODULE_EXPORT_INCLUDES += \
 	$(LIBBINDER_DIR)/ndk/include_cpp \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+BINDER_EXTRA_COMPILE_FLAGS := \
+	-D__ANDROID_VENDOR__ \
+	-D__ANDROID_VNDK__ \
+
+else
+BINDER_EXTRA_COMPILE_FLAGS := \
+	-DANDROID_PLATFORM \
+
+endif
+
 MODULE_EXPORT_COMPILEFLAGS += \
 	-DBINDER_RPC_SINGLE_THREADED \
 	-DBINDER_ENABLE_LIBLOG_ASSERT \
 	-DBINDER_DISABLE_NATIVE_HANDLE \
 	-DBINDER_DISABLE_BLOB \
 	-DBINDER_NO_LIBBASE \
-	-D__ANDROID_VENDOR__ \
-	-D__ANDROID_VNDK__ \
+	$(BINDER_EXTRA_COMPILE_FLAGS)
 
 # libbinder has some deprecated declarations that we want to produce warnings
 # not errors
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
index 2aaa061..56d711e 100644
--- a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -30,9 +30,14 @@
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
-	--cfg 'android_vendor' \
 	--cfg 'trusty' \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
+endif
+
 MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
 
 # Add the flags from the flag file
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
index 22cba44..caf3117 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
@@ -82,6 +82,9 @@
     fn repeatBinder(&self, _binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
         todo!()
     }
+    fn repeatBytes(&self, _bytes: &[u8]) -> Result<Vec<u8>, Status> {
+        todo!()
+    }
     fn holdBinder(&self, _binder: Option<&SpIBinder>) -> Result<(), Status> {
         todo!()
     }
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
index baea5a8..da1a86f 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -19,7 +19,7 @@
 use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
 use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
 use binder_rpc_test_session::MyBinderRpcSession;
-use libc::{clock_gettime, CLOCK_REALTIME};
+use libc::{clock_gettime, CLOCK_BOOTTIME};
 use rpcbinder::RpcSession;
 use trusty_std::ffi::{CString, FallibleCString};
 
@@ -56,7 +56,7 @@
     let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
 
     // Safety: Passing valid pointer to variable ts which lives past end of call
-    assert_eq!(unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) }, 0);
+    assert_eq!(unsafe { clock_gettime(CLOCK_BOOTTIME, &mut ts) }, 0);
 
     ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
 }
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
index c4a758a..6f454be 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -96,6 +96,9 @@
             None => Err(Status::from(StatusCode::BAD_VALUE)),
         }
     }
+    fn repeatBytes(&self, _bytes: &[u8]) -> Result<Vec<u8>, Status> {
+        todo!()
+    }
     fn holdBinder(&self, binder: Option<&SpIBinder>) -> Result<(), Status> {
         *HOLD_BINDER.lock().unwrap() = binder.cloned();
         Ok(())
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 97f5c03..04c63f7 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -29,8 +29,8 @@
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
-	external/rust/crates/cfg-if \
-	external/rust/crates/foreign-types \
+	$(call FIND_CRATE,cfg-if) \
+	$(call FIND_CRATE,foreign-types) \
 	trusty/user/base/lib/tipc/rust \
 	trusty/user/base/lib/trusty-sys \
 
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index 36bd3a2..acd74d2 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -27,14 +27,20 @@
 	$(LIBBINDER_DIR)/trusty/ndk \
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
-	external/rust/crates/downcast-rs \
-	external/rust/crates/libc \
+	$(call FIND_CRATE,downcast-rs) \
+	$(call FIND_CRATE,libc) \
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
-	--cfg 'android_vendor' \
 	--cfg 'trusty' \
 
+ifeq (false,$(call TOBOOL,$(USE_SYSTEM_BINDER)))
+MODULE_RUSTFLAGS += \
+	--cfg 'android_vendor' \
+
+endif
+
+
 # Trusty does not have `ProcessState`, so there are a few
 # doc links in `IBinder` that are still broken.
 MODULE_RUSTFLAGS += \
diff --git a/libs/binderdebug/stats.cpp b/libs/binderdebug/stats.cpp
index 9c26afa..972fbd5 100644
--- a/libs/binderdebug/stats.cpp
+++ b/libs/binderdebug/stats.cpp
@@ -22,9 +22,9 @@
 
 #include <inttypes.h>
 
-namespace android {
+int main() {
+    using namespace android;
 
-extern "C" int main() {
     // ignore args - we only print csv
 
     // we should use a csv library here for escaping, because
@@ -58,5 +58,3 @@
     }
     return 0;
 }
-
-} // namespace android
diff --git a/libs/binderdebug/tests/binderdebug_test.cpp b/libs/binderdebug/tests/binderdebug_test.cpp
index ea799c0..ad2b581 100644
--- a/libs/binderdebug/tests/binderdebug_test.cpp
+++ b/libs/binderdebug/tests/binderdebug_test.cpp
@@ -60,8 +60,15 @@
     EXPECT_GE(pidInfo.threadCount, 1);
 }
 
-extern "C" {
+} // namespace  test
+} // namespace  binderdebug
+} // namespace  android
+
 int main(int argc, char** argv) {
+    using namespace android;
+    using namespace android::binderdebug;
+    using namespace android::binderdebug::test;
+
     ::testing::InitGoogleTest(&argc, argv);
 
     // Create a child/client process to call into the main process so we can ensure
@@ -84,7 +91,3 @@
 
     return RUN_ALL_TESTS();
 }
-} // extern "C"
-} // namespace  test
-} // namespace  binderdebug
-} // namespace  android
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
index 17d4d87..9c48b49 100644
--- a/libs/bufferstreams/rust/src/lib.rs
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -37,23 +37,23 @@
 /// BufferPublishers are required to adhere to the following, based on the
 /// reactive streams specification:
 /// * The total number of on_next´s signalled by a Publisher to a Subscriber
-/// MUST be less than or equal to the total number of elements requested by that
-/// Subscriber´s Subscription at all times.
+///   MUST be less than or equal to the total number of elements requested by that
+///   Subscriber´s Subscription at all times.
 /// * A Publisher MAY signal fewer on_next than requested and terminate the
-/// Subscription by calling on_complete or on_error.
+///   Subscription by calling on_complete or on_error.
 /// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
-/// MUST be signaled serially.
+///   MUST be signaled serially.
 /// * If a Publisher fails it MUST signal an on_error.
 /// * If a Publisher terminates successfully (finite stream) it MUST signal an
-/// on_complete.
+///   on_complete.
 /// * If a Publisher signals either on_error or on_complete on a Subscriber,
-/// that Subscriber’s Subscription MUST be considered cancelled.
+///   that Subscriber’s Subscription MUST be considered cancelled.
 /// * Once a terminal state has been signaled (on_error, on_complete) it is
-/// REQUIRED that no further signals occur.
+///   REQUIRED that no further signals occur.
 /// * If a Subscription is cancelled its Subscriber MUST eventually stop being
-///  signaled.
+///   signaled.
 /// * A Publisher MAY support multiple Subscribers and decides whether each
-/// Subscription is unicast or multicast.
+///   Subscription is unicast or multicast.
 pub trait BufferPublisher {
     /// Returns the StreamConfig of buffers that publisher creates.
     fn get_publisher_stream_config(&self) -> StreamConfig;
@@ -69,25 +69,25 @@
 /// BufferSubcribers are required to adhere to the following, based on the
 /// reactive streams specification:
 /// * The total number of on_next´s signalled by a Publisher to a Subscriber
-/// MUST be less than or equal to the total number of elements requested by that
-/// Subscriber´s Subscription at all times.
+///   MUST be less than or equal to the total number of elements requested by that
+///   Subscriber´s Subscription at all times.
 /// * A Publisher MAY signal fewer on_next than requested and terminate the
-/// Subscription by calling on_complete or on_error.
+///   Subscription by calling on_complete or on_error.
 /// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
-/// MUST be signaled serially.
+///   MUST be signaled serially.
 /// * If a Publisher fails it MUST signal an on_error.
 /// * If a Publisher terminates successfully (finite stream) it MUST signal an
-/// on_complete.
+///   on_complete.
 /// * If a Publisher signals either on_error or on_complete on a Subscriber,
-/// that Subscriber’s Subscription MUST be considered cancelled.
+///   that Subscriber’s Subscription MUST be considered cancelled.
 /// * Once a terminal state has been signaled (on_error, on_complete) it is
-/// REQUIRED that no further signals occur.
+///   REQUIRED that no further signals occur.
 /// * If a Subscription is cancelled its Subscriber MUST eventually stop being
-/// signaled.
+///   signaled.
 /// * Publisher.subscribe MAY be called as many times as wanted but MUST be
-/// with a different Subscriber each time.
+///   with a different Subscriber each time.
 /// * A Publisher MAY support multiple Subscribers and decides whether each
-/// Subscription is unicast or multicast.
+///   Subscription is unicast or multicast.
 pub trait BufferSubscriber {
     /// The StreamConfig of buffers that this subscriber expects.
     fn get_subscriber_stream_config(&self) -> StreamConfig;
@@ -111,39 +111,39 @@
 /// BufferSubcriptions are required to adhere to the following, based on the
 /// reactive streams specification:
 /// * Subscription.request and Subscription.cancel MUST only be called inside
-/// of its Subscriber context.
+///   of its Subscriber context.
 /// * The Subscription MUST allow the Subscriber to call Subscription.request
-/// synchronously from within on_next or on_subscribe.
+///   synchronously from within on_next or on_subscribe.
 /// * Subscription.request MUST place an upper bound on possible synchronous
-/// recursion between Publisher and Subscriber.
+///   recursion between Publisher and Subscriber.
 /// * Subscription.request SHOULD respect the responsivity of its caller by
-/// returning in a timely manner.
+///   returning in a timely manner.
 /// * Subscription.cancel MUST respect the responsivity of its caller by
-/// returning in a timely manner, MUST be idempotent and MUST be thread-safe.
+///   returning in a timely manner, MUST be idempotent and MUST be thread-safe.
 /// * After the Subscription is cancelled, additional
-/// Subscription.request(n: u64) MUST be NOPs.
+///   Subscription.request(n: u64) MUST be NOPs.
 /// * After the Subscription is cancelled, additional Subscription.cancel()
-/// MUST be NOPs.
+///   MUST be NOPs.
 /// * While the Subscription is not cancelled, Subscription.request(n: u64)
-/// MUST register the given number of additional elements to be produced to the
-/// respective subscriber.
+///   MUST register the given number of additional elements to be produced to the
+///   respective subscriber.
 /// * While the Subscription is not cancelled, Subscription.request(n: u64)
-/// MUST signal on_error if the argument is <= 0. The cause message SHOULD
-/// explain that non-positive request signals are illegal.
+///   MUST signal on_error if the argument is <= 0. The cause message SHOULD
+///   explain that non-positive request signals are illegal.
 /// * While the Subscription is not cancelled, Subscription.request(n: u64)
-/// MAY synchronously call on_next on this (or other) subscriber(s).
+///   MAY synchronously call on_next on this (or other) subscriber(s).
 /// * While the Subscription is not cancelled, Subscription.request(n: u64)
-/// MAY synchronously call on_complete or on_error on this (or other)
-/// subscriber(s).
+///   MAY synchronously call on_complete or on_error on this (or other)
+///   subscriber(s).
 /// * While the Subscription is not cancelled, Subscription.cancel() MUST
-/// request the Publisher to eventually stop signaling its Subscriber. The
-/// operation is NOT REQUIRED to affect the Subscription immediately.
+///   request the Publisher to eventually stop signaling its Subscriber. The
+///   operation is NOT REQUIRED to affect the Subscription immediately.
 /// * While the Subscription is not cancelled, Subscription.cancel() MUST
-/// request the Publisher to eventually drop any references to the corresponding
-/// subscriber.
+///   request the Publisher to eventually drop any references to the corresponding
+///   subscriber.
 /// * While the Subscription is not cancelled, calling Subscription.cancel MAY
-/// cause the Publisher, if stateful, to transition into the shut-down state if
-/// no other Subscription exists at this point.
+///   cause the Publisher, if stateful, to transition into the shut-down state if
+///   no other Subscription exists at this point.
 /// * Calling Subscription.cancel MUST return normally.
 /// * Calling Subscription.request MUST return normally.
 pub trait BufferSubscription: Send + Sync + 'static {
diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs
index 454bdf1..8288f9f 100644
--- a/libs/bufferstreams/rust/src/stream_config.rs
+++ b/libs/bufferstreams/rust/src/stream_config.rs
@@ -32,10 +32,23 @@
     pub stride: u32,
 }
 
+impl From<StreamConfig> for HardwareBufferDescription {
+    fn from(config: StreamConfig) -> Self {
+        HardwareBufferDescription::new(
+            config.width,
+            config.height,
+            config.layers,
+            config.format,
+            config.usage,
+            config.stride,
+        )
+    }
+}
+
 impl StreamConfig {
     /// Tries to create a new HardwareBuffer from settings in a [StreamConfig].
     pub fn create_hardware_buffer(&self) -> Option<HardwareBuffer> {
-        HardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage)
+        HardwareBuffer::new(&(*self).into())
     }
 }
 
@@ -59,9 +72,10 @@
         assert!(maybe_buffer.is_some());
 
         let buffer = maybe_buffer.unwrap();
-        assert_eq!(config.width, buffer.width());
-        assert_eq!(config.height, buffer.height());
-        assert_eq!(config.format, buffer.format());
-        assert_eq!(config.usage, buffer.usage());
+        let description = buffer.description();
+        assert_eq!(config.width, description.width());
+        assert_eq!(config.height, description.height());
+        assert_eq!(config.format, description.format());
+        assert_eq!(config.usage, description.usage());
     }
 }
diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS
index 428a1a2..c8e22b7 100644
--- a/libs/debugstore/OWNERS
+++ b/libs/debugstore/OWNERS
@@ -1,3 +1,2 @@
 benmiles@google.com
-gaillard@google.com
 mohamadmahmoud@google.com
diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp
index 55ba3c3..9475333 100644
--- a/libs/debugstore/rust/Android.bp
+++ b/libs/debugstore/rust/Android.bp
@@ -23,7 +23,6 @@
     rustlibs: [
         "libcrossbeam_queue",
         "libparking_lot",
-        "libonce_cell",
         "libcxx",
     ],
     shared_libs: ["libutils"],
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
index 1dfa512..6bf79d4 100644
--- a/libs/debugstore/rust/src/core.rs
+++ b/libs/debugstore/rust/src/core.rs
@@ -17,12 +17,14 @@
 use super::event_type::EventType;
 use super::storage::Storage;
 use crate::cxxffi::uptimeMillis;
-use once_cell::sync::Lazy;
 use std::fmt;
-use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::{
+    atomic::{AtomicU64, Ordering},
+    LazyLock,
+};
 
 //  Lazily initialized static instance of DebugStore.
-static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new);
+static INSTANCE: LazyLock<DebugStore> = LazyLock::new(DebugStore::new);
 
 /// The `DebugStore` struct is responsible for managing debug events and data.
 pub struct DebugStore {
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
index f62241d..f2b2aa7 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
@@ -65,6 +65,7 @@
 
     std::vector<IServiceManager::ServiceDebugInfo> getServiceDebugInfo() override;
 
+    void enableAddServiceCache(bool /*value*/) override {}
     // Clear all of the registered services
     void clear();
 
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index 32b2b68..368f5e0 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -24,6 +24,7 @@
         "flags_test.cpp",
         "function_test.cpp",
         "future_test.cpp",
+        "hash_test.cpp",
         "match_test.cpp",
         "mixins_test.cpp",
         "non_null_test.cpp",
@@ -40,5 +41,6 @@
         "-Wextra",
         "-Wpedantic",
         "-Wthread-safety",
+        "-Wno-gnu-statement-expression-from-macro-expansion",
     ],
 }
diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp
index 487b1b8..11569f2 100644
--- a/libs/ftl/algorithm_test.cpp
+++ b/libs/ftl/algorithm_test.cpp
@@ -24,6 +24,17 @@
 namespace android::test {
 
 // Keep in sync with example usage in header file.
+TEST(Algorithm, Contains) {
+  const ftl::StaticVector vector = {1, 2, 3};
+  EXPECT_TRUE(ftl::contains(vector, 1));
+
+  EXPECT_FALSE(ftl::contains(vector, 0));
+  EXPECT_TRUE(ftl::contains(vector, 2));
+  EXPECT_TRUE(ftl::contains(vector, 3));
+  EXPECT_FALSE(ftl::contains(vector, 4));
+}
+
+// Keep in sync with example usage in header file.
 TEST(Algorithm, FindIf) {
   using namespace std::string_view_literals;
 
diff --git a/libs/ftl/expected_test.cpp b/libs/ftl/expected_test.cpp
index 8cb07e4..d5b1d7e 100644
--- a/libs/ftl/expected_test.cpp
+++ b/libs/ftl/expected_test.cpp
@@ -15,8 +15,11 @@
  */
 
 #include <ftl/expected.h>
+#include <ftl/optional.h>
+#include <ftl/unit.h>
 #include <gtest/gtest.h>
 
+#include <cctype>
 #include <string>
 #include <system_error>
 
@@ -74,4 +77,69 @@
   }
 }
 
+namespace {
+
+IntExp increment_try(IntExp exp) {
+  const int i = FTL_TRY(exp);
+  return IntExp(i + 1);
+}
+
+std::errc increment_expect(IntExp exp, int& out) {
+  const int i = FTL_EXPECT(exp);
+  out = i + 1;
+  return std::errc::operation_in_progress;
+}
+
+StringExp repeat_try(StringExp exp) {
+  const std::string str = FTL_TRY(exp);
+  return StringExp(str + str);
+}
+
+std::errc repeat_expect(StringExp exp, std::string& out) {
+  const std::string str = FTL_EXPECT(exp);
+  out = str + str;
+  return std::errc::operation_in_progress;
+}
+
+void uppercase(char& c, ftl::Optional<char> opt) {
+  c = std::toupper(FTL_TRY(std::move(opt).ok_or(ftl::Unit())));
+}
+
+}  // namespace
+
+// Keep in sync with example usage in header file.
+TEST(Expected, Try) {
+  EXPECT_EQ(IntExp(100), increment_try(IntExp(99)));
+  EXPECT_TRUE(increment_try(ftl::Unexpected(std::errc::value_too_large)).has_error([](std::errc e) {
+    return e == std::errc::value_too_large;
+  }));
+
+  EXPECT_EQ(StringExp("haha"s), repeat_try(StringExp("ha"s)));
+  EXPECT_TRUE(repeat_try(ftl::Unexpected(std::errc::bad_message)).has_error([](std::errc e) {
+    return e == std::errc::bad_message;
+  }));
+
+  char c = '?';
+  uppercase(c, std::nullopt);
+  EXPECT_EQ(c, '?');
+
+  uppercase(c, 'a');
+  EXPECT_EQ(c, 'A');
+}
+
+TEST(Expected, Expect) {
+  int i = 0;
+  EXPECT_EQ(std::errc::operation_in_progress, increment_expect(IntExp(99), i));
+  EXPECT_EQ(100, i);
+  EXPECT_EQ(std::errc::value_too_large,
+            increment_expect(ftl::Unexpected(std::errc::value_too_large), i));
+  EXPECT_EQ(100, i);
+
+  std::string str;
+  EXPECT_EQ(std::errc::operation_in_progress, repeat_expect(StringExp("ha"s), str));
+  EXPECT_EQ("haha"s, str);
+  EXPECT_EQ(std::errc::bad_message, repeat_expect(ftl::Unexpected(std::errc::bad_message), str));
+  EXPECT_EQ("haha"s, str);
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/hash_test.cpp b/libs/ftl/hash_test.cpp
new file mode 100644
index 0000000..9c7b8c2
--- /dev/null
+++ b/libs/ftl/hash_test.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ftl/hash.h>
+#include <gtest/gtest.h>
+
+#include <numeric>
+#include <string>
+
+namespace android::test {
+
+TEST(Hash, StableHash) {
+  EXPECT_EQ(11160318154034397263ull, (ftl::stable_hash({})));
+
+  std::string string(64, '?');
+  std::iota(string.begin(), string.end(), 'A');
+
+  // Maximum length is 64 characters.
+  EXPECT_FALSE(ftl::stable_hash(string + '\n'));
+
+  EXPECT_EQ(6278090252846864564ull, ftl::stable_hash(std::string_view(string).substr(0, 8)));
+  EXPECT_EQ(1883356980931444616ull, ftl::stable_hash(std::string_view(string).substr(0, 16)));
+  EXPECT_EQ(8073093283835059304ull, ftl::stable_hash(std::string_view(string).substr(0, 32)));
+  EXPECT_EQ(18197365392429149980ull, ftl::stable_hash(string));
+}
+
+}  // namespace android::test
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index bd0462b..367b398 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
+#include <ftl/algorithm.h>
 #include <ftl/non_null.h>
 #include <gtest/gtest.h>
 
 #include <memory>
+#include <set>
 #include <string>
 #include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <vector>
 
 namespace android::test {
 namespace {
@@ -47,7 +52,7 @@
 // Keep in sync with example usage in header file.
 TEST(NonNull, Example) {
   const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
-  std::size_t size;
+  std::size_t size{};
   get_length(string_ptr, ftl::as_non_null(&size));
   EXPECT_EQ(size, 7u);
 
@@ -71,5 +76,84 @@
 
 static_assert(longest(kApplePtr, kOrangePtr) == kOrangePtr);
 
+static_assert(static_cast<bool>(kApplePtr));
+
+static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
+                             ftl::NonNull<const int*>>);
+
 }  // namespace
+
+TEST(NonNull, SwapRawPtr) {
+  int i1 = 123;
+  int i2 = 456;
+  auto ptr1 = ftl::as_non_null(&i1);
+  auto ptr2 = ftl::as_non_null(&i2);
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, SwapSmartPtr) {
+  auto ptr1 = ftl::as_non_null(std::make_shared<int>(123));
+  auto ptr2 = ftl::as_non_null(std::make_shared<int>(456));
+
+  std::swap(ptr1, ptr2);
+
+  EXPECT_EQ(*ptr1, 456);
+  EXPECT_EQ(*ptr2, 123);
+}
+
+TEST(NonNull, VectorOfRawPtr) {
+  int i = 1;
+  std::vector<ftl::NonNull<int*>> vpi;
+  vpi.push_back(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, &i));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, VectorOfSmartPtr) {
+  std::vector<ftl::NonNull<std::shared_ptr<int>>> vpi;
+  vpi.push_back(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(vpi, nullptr));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front().get()));
+  EXPECT_TRUE(ftl::contains(vpi, vpi.front()));
+}
+
+TEST(NonNull, SetOfRawPtr) {
+  int i = 1;
+  std::set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, SetOfSmartPtr) {
+  std::set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfRawPtr) {
+  int i = 1;
+  std::unordered_set<ftl::NonNull<int*>> spi;
+  spi.insert(ftl::as_non_null(&i));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, &i));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
+TEST(NonNull, UnorderedSetOfSmartPtr) {
+  std::unordered_set<ftl::NonNull<std::shared_ptr<int>>> spi;
+  spi.insert(ftl::as_non_null(std::make_shared<int>(2)));
+  EXPECT_FALSE(ftl::contains(spi, nullptr));
+  EXPECT_TRUE(ftl::contains(spi, spi.begin()->get()));
+  EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
+}
+
 }  // namespace android::test
diff --git a/libs/ftl/optional_test.cpp b/libs/ftl/optional_test.cpp
index 91bf7bc..e7f1f14 100644
--- a/libs/ftl/optional_test.cpp
+++ b/libs/ftl/optional_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <ftl/expected.h>
 #include <ftl/optional.h>
 #include <ftl/static_vector.h>
 #include <ftl/string.h>
@@ -23,6 +24,7 @@
 #include <cstdlib>
 #include <functional>
 #include <numeric>
+#include <system_error>
 #include <utility>
 
 using namespace std::placeholders;
@@ -204,6 +206,19 @@
                      .or_else([] { return Optional(-1); }));
 }
 
+TEST(Optional, OkOr) {
+  using CharExp = ftl::Expected<char, std::errc>;
+  using StringExp = ftl::Expected<std::string, std::errc>;
+
+  EXPECT_EQ(CharExp('z'), Optional('z').ok_or(std::errc::broken_pipe));
+  EXPECT_EQ(CharExp(ftl::Unexpected(std::errc::broken_pipe)),
+            Optional<char>().ok_or(std::errc::broken_pipe));
+
+  EXPECT_EQ(StringExp("abc"s), Optional("abc"s).ok_or(std::errc::protocol_error));
+  EXPECT_EQ(StringExp(ftl::Unexpected(std::errc::protocol_error)),
+            Optional<std::string>().ok_or(std::errc::protocol_error));
+}
+
 // Comparison.
 namespace {
 
diff --git a/libs/gralloc/types/Android.bp b/libs/gralloc/types/Android.bp
index 8dabc2c..f9f304a 100644
--- a/libs/gralloc/types/Android.bp
+++ b/libs/gralloc/types/Android.bp
@@ -56,7 +56,7 @@
     ],
 
     export_shared_lib_headers: [
-        "android.hardware.graphics.common-V5-ndk",
+        "android.hardware.graphics.common-V6-ndk",
         "android.hardware.graphics.mapper@4.0",
         "libhidlbase",
     ],
diff --git a/libs/gralloc/types/fuzzer/Android.bp b/libs/gralloc/types/fuzzer/Android.bp
index 8337182..d9cdb59 100644
--- a/libs/gralloc/types/fuzzer/Android.bp
+++ b/libs/gralloc/types/fuzzer/Android.bp
@@ -28,14 +28,10 @@
     ],
     static_libs: [
         "libbase",
-        "libcgrouprc",
-        "libcgrouprc_format",
         "libcutils",
         "libgralloctypes",
         "libhidlbase",
         "liblog",
-        "libprocessgroup",
-        "libjsoncpp",
         "libutils",
     ],
 
diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp
index 7b74214..33cebe3 100644
--- a/libs/graphicsenv/GpuStatsInfo.cpp
+++ b/libs/graphicsenv/GpuStatsInfo.cpp
@@ -96,6 +96,7 @@
     if ((status = parcel->writeUint64(vulkanDeviceFeaturesEnabled)) != OK) return status;
     if ((status = parcel->writeInt32Vector(vulkanInstanceExtensions)) != OK) return status;
     if ((status = parcel->writeInt32Vector(vulkanDeviceExtensions)) != OK) return status;
+    if ((status = parcel->writeUtf8VectorAsUtf16Vector(vulkanEngineNames)) != OK) return status;
 
     return OK;
 }
@@ -118,6 +119,7 @@
     if ((status = parcel->readUint64(&vulkanDeviceFeaturesEnabled)) != OK) return status;
     if ((status = parcel->readInt32Vector(&vulkanInstanceExtensions)) != OK) return status;
     if ((status = parcel->readInt32Vector(&vulkanDeviceExtensions)) != OK) return status;
+    if ((status = parcel->readUtf8VectorFromUtf16Vector(&vulkanEngineNames)) != OK) return status;
 
     return OK;
 }
@@ -161,6 +163,11 @@
         StringAppendF(&result, " 0x%x", extension);
     }
     result.append("\n");
+    result.append("vulkanEngineNames:");
+    for (const std::string& engineName : vulkanEngineNames) {
+        StringAppendF(&result, " %s,", engineName.c_str());
+    }
+    result.append("\n");
     return result;
 }
 
diff --git a/libs/graphicsenv/GraphicsEnv.cpp b/libs/graphicsenv/GraphicsEnv.cpp
index 50c05f4..4874dbd 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -83,6 +83,55 @@
 
 static const char* kLlndkLibrariesTxtPath = "/system/etc/llndk.libraries.txt";
 
+// List of libraries that were previously available via VNDK-SP,
+// and are now available via SPHAL.
+// On modern devices that lack the VNDK APEX, the device no longer
+// contains a helpful list of these libraries on the filesystem as above.
+// See system/sepolicy/vendor/file_contexts
+static const char* kFormerlyVndkspLibrariesList =
+    "android.hardware.common-V2-ndk.so:"
+    "android.hardware.common.fmq-V1-ndk.so:"
+    "android.hardware.graphics.allocator-V2-ndk.so:"
+    "android.hardware.graphics.common-V6-ndk.so:"
+    "android.hardware.graphics.common@1.0.so:"
+    "android.hardware.graphics.common@1.1.so:"
+    "android.hardware.graphics.common@1.2.so:"
+    "android.hardware.graphics.composer3-V1-ndk.so:"
+    "android.hardware.graphics.mapper@2.0.so:"
+    "android.hardware.graphics.mapper@2.1.so:"
+    "android.hardware.graphics.mapper@3.0.so:"
+    "android.hardware.graphics.mapper@4.0.so:"
+    "android.hardware.renderscript@1.0.so:"
+    "android.hidl.memory.token@1.0.so:"
+    "android.hidl.memory@1.0-impl.so:"
+    "android.hidl.memory@1.0.so:"
+    "android.hidl.safe_union@1.0.so:"
+    "libRSCpuRef.so:"
+    "libRSDriver.so:"
+    "libRS_internal.so:"
+    "libbacktrace.so:"
+    "libbase.so:"
+    "libbcinfo.so:"
+    "libblas.so:"
+    "libc++.so:"
+    "libcompiler_rt.so:"
+    "libcutils.so:"
+    "libdmabufheap.so:"
+    "libft2.so:"
+    "libgralloctypes.so:"
+    "libhardware.so:"
+    "libhidlbase.so:"
+    "libhidlmemory.so:"
+    "libion.so:"
+    "libjsoncpp.so:"
+    "liblzma.so:"
+    "libpng.so:"
+    "libprocessgroup.so:"
+    "libunwindstack.so:"
+    "libutils.so:"
+    "libutilscallstack.so:"
+    "libz.so";
+
 static std::string vndkVersionStr() {
 #ifdef __BIONIC__
     return base::GetProperty("ro.vndk.version", "");
@@ -122,8 +171,12 @@
 static const std::string getSystemNativeLibraries(NativeLibrary type) {
     std::string nativeLibrariesSystemConfig = "";
 
-    if (!isVndkEnabled() && type == NativeLibrary::LLNDK) {
-        nativeLibrariesSystemConfig = kLlndkLibrariesTxtPath;
+    if (!isVndkEnabled()) {
+        if (type == NativeLibrary::VNDKSP) {
+            return kFormerlyVndkspLibrariesList;
+        } else {
+            nativeLibrariesSystemConfig = kLlndkLibrariesTxtPath;
+        }
     } else {
         nativeLibrariesSystemConfig = kNativeLibrariesSystemConfigPath[type];
         insertVndkVersionStr(&nativeLibrariesSystemConfig);
@@ -263,7 +316,7 @@
         ALOGI("Driver path is setup via UPDATABLE_GFX_DRIVER: %s", mDriverPath.c_str());
     }
 
-    auto vndkNamespace = android_get_exported_namespace("vndk");
+    auto vndkNamespace = android_get_exported_namespace(isVndkEnabled() ? "vndk" : "sphal");
     if (!vndkNamespace) {
         mDriverNamespace = nullptr;
         return mDriverNamespace;
@@ -348,18 +401,10 @@
     switch (driver) {
         case GpuStatsInfo::Driver::GL:
         case GpuStatsInfo::Driver::GL_UPDATED:
-        case GpuStatsInfo::Driver::ANGLE: {
-            if (mGpuStats.glDriverToLoad == GpuStatsInfo::Driver::NONE ||
-                mGpuStats.glDriverToLoad == GpuStatsInfo::Driver::GL) {
-                mGpuStats.glDriverToLoad = driver;
-                break;
-            }
-
-            if (mGpuStats.glDriverFallback == GpuStatsInfo::Driver::NONE) {
-                mGpuStats.glDriverFallback = driver;
-            }
+        case GpuStatsInfo::Driver::ANGLE:
+            mGpuStats.glDriverToLoad = driver;
             break;
-        }
+
         case GpuStatsInfo::Driver::VULKAN:
         case GpuStatsInfo::Driver::VULKAN_UPDATED: {
             if (mGpuStats.vkDriverToLoad == GpuStatsInfo::Driver::NONE ||
@@ -445,6 +490,21 @@
                         extensionHashes, numStats);
 }
 
+void GraphicsEnv::addVulkanEngineName(const char* engineName) {
+    ATRACE_CALL();
+    if (engineName == nullptr) {
+        return;
+    }
+    std::lock_guard<std::mutex> lock(mStatsLock);
+    if (!readyToSendGpuStatsLocked()) return;
+
+    const sp<IGpuService> gpuService = getGpuService();
+    if (gpuService) {
+        gpuService->addVulkanEngineName(mGpuStats.appPackageName, mGpuStats.driverVersionCode,
+                                        engineName);
+    }
+}
+
 bool GraphicsEnv::readyToSendGpuStatsLocked() {
     // Only send stats for processes having at least one activity launched and that process doesn't
     // skip the GraphicsEnvironment setup.
@@ -493,8 +553,7 @@
     bool isIntendedDriverLoaded = false;
     if (api == GpuStatsInfo::Api::API_GL) {
         driver = mGpuStats.glDriverToLoad;
-        isIntendedDriverLoaded =
-                isDriverLoaded && (mGpuStats.glDriverFallback == GpuStatsInfo::Driver::NONE);
+        isIntendedDriverLoaded = isDriverLoaded;
     } else {
         driver = mGpuStats.vkDriverToLoad;
         isIntendedDriverLoaded =
@@ -537,7 +596,7 @@
 // If path is set to nonempty and shouldUseNativeDriver is true, ANGLE will be used regardless.
 void GraphicsEnv::setAngleInfo(const std::string& path, const bool shouldUseNativeDriver,
                                const std::string& packageName,
-                               const std::vector<std::string> eglFeatures) {
+                               const std::vector<std::string>& eglFeatures) {
     if (mShouldUseAngle) {
         // ANGLE is already set up for this application process, even if the application
         // needs to switch from apk to system or vice versa, the application process must
@@ -547,11 +606,11 @@
         return;
     }
 
-    mAngleEglFeatures = std::move(eglFeatures);
+    mAngleEglFeatures = eglFeatures;
     ALOGV("setting ANGLE path to '%s'", path.c_str());
-    mAnglePath = std::move(path);
+    mAnglePath = path;
     ALOGV("setting app package name to '%s'", packageName.c_str());
-    mPackageName = std::move(packageName);
+    mPackageName = packageName;
     if (mAnglePath == "system") {
         mShouldUseSystemAngle = true;
     }
@@ -616,7 +675,7 @@
         return mAngleNamespace;
     }
 
-    auto vndkNamespace = android_get_exported_namespace("vndk");
+    auto vndkNamespace = android_get_exported_namespace(isVndkEnabled() ? "vndk" : "sphal");
     if (!vndkNamespace) {
         mAngleNamespace = nullptr;
         return mAngleNamespace;
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index 5dc195c..42e7c37 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -77,6 +77,19 @@
                            IBinder::FLAG_ONEWAY);
     }
 
+    void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode,
+                             const char* engineName) override {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+        data.writeUtf8AsUtf16(appPackageName);
+        data.writeUint64(driverVersionCode);
+        data.writeCString(engineName);
+
+        remote()->transact(BnGpuService::ADD_VULKAN_ENGINE_NAME, data, &reply,
+                           IBinder::FLAG_ONEWAY);
+    }
+
     void setUpdatableDriverPath(const std::string& driverPath) override {
         Parcel data, reply;
         data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
@@ -197,6 +210,21 @@
 
             return OK;
         }
+        case ADD_VULKAN_ENGINE_NAME: {
+            CHECK_INTERFACE(IGpuService, data, reply);
+
+            std::string appPackageName;
+            if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status;
+
+            uint64_t driverVersionCode;
+            if ((status = data.readUint64(&driverVersionCode)) != OK) return status;
+
+            const char* engineName;
+            if ((engineName = data.readCString()) == nullptr) return BAD_VALUE;
+
+            addVulkanEngineName(appPackageName, driverVersionCode, engineName);
+            return OK;
+        }
         case SET_UPDATABLE_DRIVER_PATH: {
             CHECK_INTERFACE(IGpuService, data, reply);
 
diff --git a/libs/graphicsenv/OWNERS b/libs/graphicsenv/OWNERS
index 1db8cbe..4aa8fff 100644
--- a/libs/graphicsenv/OWNERS
+++ b/libs/graphicsenv/OWNERS
@@ -1,4 +1,11 @@
 chrisforbes@google.com
-cnorthrop@google.com
 ianelliott@google.com
-lpy@google.com
+
+abdolrashidi@google.com
+cclao@google.com
+cnorthrop@google.com
+hibrian@google.com
+mathias@google.com
+romanl@google.com
+solti@google.com
+yuxinhu@google.com
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
index 9ebaf16..72f29c6 100644
--- a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
+++ b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
@@ -60,6 +60,10 @@
 public:
     // This limits the worst case number of extensions to be tracked.
     static const uint32_t MAX_NUM_EXTENSIONS = 100;
+    // Max number of vulkan engine names for a single GpuStatsAppInfo
+    static const uint32_t MAX_VULKAN_ENGINE_NAMES = 16;
+    // Max length of a vulkan engine name string
+    static const size_t MAX_VULKAN_ENGINE_NAME_LENGTH = 50;
 
     GpuStatsAppInfo() = default;
     GpuStatsAppInfo(const GpuStatsAppInfo&) = default;
@@ -84,6 +88,7 @@
     uint64_t vulkanDeviceFeaturesEnabled = 0;
     std::vector<int32_t> vulkanInstanceExtensions = {};
     std::vector<int32_t> vulkanDeviceExtensions = {};
+    std::vector<std::string> vulkanEngineNames = {};
 
     std::chrono::time_point<std::chrono::system_clock> lastAccessTime;
 };
@@ -120,6 +125,11 @@
         VULKAN_DEVICE_EXTENSION = 9,
     };
 
+    enum GLTelemetryHints {
+        NO_HINT = 0,
+        SKIP_TELEMETRY = 1,
+    };
+
     GpuStatsInfo() = default;
     GpuStatsInfo(const GpuStatsInfo&) = default;
     virtual ~GpuStatsInfo() = default;
@@ -131,7 +141,6 @@
     std::string appPackageName = "";
     int32_t vulkanVersion = 0;
     Driver glDriverToLoad = Driver::NONE;
-    Driver glDriverFallback = Driver::NONE;
     Driver vkDriverToLoad = Driver::NONE;
     Driver vkDriverFallback = Driver::NONE;
     bool glDriverToSend = false;
diff --git a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
index 6cce3f6..452e48b 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -89,6 +89,8 @@
     // Set which device extensions are enabled for the app.
     void setVulkanDeviceExtensions(uint32_t enabledExtensionCount,
                                    const char* const* ppEnabledExtensionNames);
+    // Add the engine name passed in VkApplicationInfo during CreateInstance
+    void addVulkanEngineName(const char* engineName);
 
     /*
      * Api for Vk/GL layer injection.  Presently, drivers enable certain
@@ -112,7 +114,7 @@
     // If shouldUseNativeDriver is true, it means native GLES drivers must be used for the process.
     // If path is set to nonempty and shouldUseNativeDriver is true, ANGLE will be used regardless.
     void setAngleInfo(const std::string& path, const bool shouldUseNativeDriver,
-                      const std::string& packageName, const std::vector<std::string> eglFeatures);
+                      const std::string& packageName, const std::vector<std::string>& eglFeatures);
     // Get the ANGLE driver namespace.
     android_namespace_t* getAngleNamespace();
     // Get the app package name.
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index 45f05d6..a0d6e37 100644
--- a/libs/graphicsenv/include/graphicsenv/IGpuService.h
+++ b/libs/graphicsenv/include/graphicsenv/IGpuService.h
@@ -46,6 +46,8 @@
                                      const uint64_t driverVersionCode,
                                      const GpuStatsInfo::Stats stats, const uint64_t* values,
                                      const uint32_t valueCount) = 0;
+    virtual void addVulkanEngineName(const std::string& appPackageName,
+                                     const uint64_t driverVersionCode, const char* engineName) = 0;
 
     // setter and getter for updatable driver path.
     virtual void setUpdatableDriverPath(const std::string& driverPath) = 0;
@@ -64,6 +66,7 @@
         GET_UPDATABLE_DRIVER_PATH,
         TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
         SET_TARGET_STATS_ARRAY,
+        ADD_VULKAN_ENGINE_NAME,
         // Always append new enum to the end.
     };
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 7dcbbc0..1243b21 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -41,6 +41,11 @@
     aconfig_declarations: "libgui_flags",
 }
 
+cc_aconfig_library {
+    name: "libguiflags_no_apex",
+    aconfig_declarations: "libgui_flags",
+}
+
 cc_library_headers {
     name: "libgui_headers",
     vendor_available: true,
@@ -87,6 +92,7 @@
         "android/gui/DropInputMode.aidl",
         "android/gui/StalledTransactionInfo.aidl",
         "android/**/TouchOcclusionMode.aidl",
+        "android/gui/TrustedOverlay.aidl",
     ],
 }
 
@@ -155,9 +161,9 @@
     },
 }
 
-aidl_library {
-    name: "libgui_aidl_hdrs",
-    hdrs: [
+filegroup {
+    name: "libgui_extra_aidl_files",
+    srcs: [
         "android/gui/DisplayInfo.aidl",
         "android/gui/FocusRequest.aidl",
         "android/gui/InputApplicationInfo.aidl",
@@ -170,11 +176,34 @@
     ],
 }
 
+filegroup {
+    name: "libgui_extra_unstructured_aidl_files",
+    srcs: [
+        "android/gui/DisplayInfo.aidl",
+        "android/gui/InputApplicationInfo.aidl",
+        "android/gui/WindowInfo.aidl",
+        "android/gui/WindowInfosUpdate.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_aidl_hdrs",
+    hdrs: [":libgui_extra_aidl_files"],
+}
+
+aidl_library {
+    name: "libgui_extra_unstructured_aidl_hdrs",
+    hdrs: [":libgui_extra_unstructured_aidl_files"],
+}
+
 aidl_library {
     name: "libgui_aidl",
     srcs: ["aidl/**/*.aidl"],
     strip_import_prefix: "aidl",
-    deps: ["libgui_aidl_hdrs"],
+    deps: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
 }
 
 filegroup {
@@ -226,6 +255,7 @@
         "BitTube.cpp",
         "BLASTBufferQueue.cpp",
         "BufferItemConsumer.cpp",
+        "BufferReleaseChannel.cpp",
         "Choreographer.cpp",
         "CompositorTiming.cpp",
         "ConsumerBase.cpp",
@@ -241,7 +271,6 @@
         "IProducerListener.cpp",
         "ISurfaceComposer.cpp",
         "ITransactionCompletedListener.cpp",
-        "LayerDebugInfo.cpp",
         "LayerMetadata.cpp",
         "LayerStatePermissions.cpp",
         "LayerState.cpp",
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index f317a2e..ba82046 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -20,12 +20,16 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 //#define LOG_NDEBUG 0
 
+#include <com_android_graphics_libgui_flags.h>
 #include <cutils/atomic.h>
+#include <ftl/fake_guard.h>
 #include <gui/BLASTBufferQueue.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/BufferQueueConsumer.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
 
 #include <gui/FrameRateUtils.h>
 #include <gui/GLConsumer.h>
@@ -39,7 +43,6 @@
 #include <private/gui/ComposerServiceAIDL.h>
 
 #include <android-base/thread_annotations.h>
-#include <chrono>
 
 #include <com_android_graphics_libgui_flags.h>
 
@@ -74,6 +77,12 @@
     std::unique_lock _lock{mutex};        \
     base::ScopedLockAssertion assumeLocked(mutex);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+static ReleaseBufferCallback EMPTY_RELEASE_CALLBACK =
+        [](const ReleaseCallbackId&, const sp<Fence>& /*releaseFence*/,
+           std::optional<uint32_t> /*currentMaxAcquiredBufferCount*/) {};
+#endif
+
 void BLASTBufferItemConsumer::onDisconnect() {
     Mutex::Autolock lock(mMutex);
     mPreviouslyConnected = mCurrentlyConnected;
@@ -175,16 +184,21 @@
         mSyncTransaction(nullptr),
         mUpdateDestinationFrame(updateDestinationFrame) {
     createBufferQueue(&mProducer, &mConsumer);
-    // since the adapter is in the client process, set dequeue timeout
-    // explicitly so that dequeueBuffer will block
-    mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());
-
-    // safe default, most producers are expected to override this
-    mProducer->setMaxDequeuedBufferCount(2);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    mBufferItemConsumer = new BLASTBufferItemConsumer(mProducer, mConsumer,
+                                                      GraphicBuffer::USAGE_HW_COMPOSER |
+                                                              GraphicBuffer::USAGE_HW_TEXTURE,
+                                                      1, false, this);
+#else
     mBufferItemConsumer = new BLASTBufferItemConsumer(mConsumer,
                                                       GraphicBuffer::USAGE_HW_COMPOSER |
                                                               GraphicBuffer::USAGE_HW_TEXTURE,
                                                       1, false, this);
+#endif //  COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    // since the adapter is in the client process, set dequeue timeout
+    // explicitly so that dequeueBuffer will block
+    mProducer->setDequeueTimeout(std::numeric_limits<int64_t>::max());
+
     static std::atomic<uint32_t> nextId = 0;
     mProducerId = nextId++;
     mName = name + "#" + std::to_string(mProducerId);
@@ -196,8 +210,6 @@
     ComposerServiceAIDL::getComposerService()->getMaxAcquiredBufferCount(&mMaxAcquiredBuffers);
     mBufferItemConsumer->setMaxAcquiredBufferCount(mMaxAcquiredBuffers);
     mCurrentMaxAcquiredBufferCount = mMaxAcquiredBuffers;
-    mNumAcquired = 0;
-    mNumFrameAvailable = 0;
 
     TransactionCompletedListener::getInstance()->addQueueStallListener(
             [&](const std::string& reason) {
@@ -210,6 +222,12 @@
             },
             this);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+    std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> bufferReleaseConsumer;
+    gui::BufferReleaseChannel::open(mName, bufferReleaseConsumer, mBufferReleaseProducer);
+    mBufferReleaseReader = std::make_shared<BufferReleaseReader>(std::move(bufferReleaseConsumer));
+#endif
+
     BQA_LOGV("BLASTBufferQueue created");
 }
 
@@ -236,6 +254,14 @@
     }
 }
 
+void BLASTBufferQueue::onFirstRef() {
+    // safe default, most producers are expected to override this
+    mProducer->setMaxDequeuedBufferCount(2);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+    mBufferReleaseThread.start(sp<BLASTBufferQueue>::fromExisting(this));
+#endif
+}
+
 void BLASTBufferQueue::update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height,
                               int32_t format) {
     LOG_ALWAYS_FATAL_IF(surface == nullptr, "BLASTBufferQueue: mSurfaceControl must not be NULL");
@@ -259,6 +285,9 @@
     if (surfaceControlChanged) {
         t.setFlags(mSurfaceControl, layer_state_t::eEnableBackpressure,
                    layer_state_t::eEnableBackpressure);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+        t.setBufferReleaseChannel(mSurfaceControl, mBufferReleaseProducer);
+#endif
         applyTransaction = true;
     }
     mTransformHint = mSurfaceControl->getTransformHint();
@@ -296,14 +325,12 @@
     return std::nullopt;
 }
 
-static void transactionCommittedCallbackThunk(void* context, nsecs_t latchTime,
-                                              const sp<Fence>& presentFence,
-                                              const std::vector<SurfaceControlStats>& stats) {
-    if (context == nullptr) {
-        return;
-    }
-    sp<BLASTBufferQueue> bq = static_cast<BLASTBufferQueue*>(context);
-    bq->transactionCommittedCallback(latchTime, presentFence, stats);
+TransactionCompletedCallbackTakesContext BLASTBufferQueue::makeTransactionCommittedCallbackThunk() {
+    return [bbq = sp<BLASTBufferQueue>::fromExisting(
+                    this)](void* /*context*/, nsecs_t latchTime, const sp<Fence>& presentFence,
+                           const std::vector<SurfaceControlStats>& stats) {
+        bbq->transactionCommittedCallback(latchTime, presentFence, stats);
+    };
 }
 
 void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/,
@@ -336,18 +363,15 @@
             BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was "
                      "empty.");
         }
-        decStrong((void*)transactionCommittedCallbackThunk);
     }
 }
 
-static void transactionCallbackThunk(void* context, nsecs_t latchTime,
-                                     const sp<Fence>& presentFence,
-                                     const std::vector<SurfaceControlStats>& stats) {
-    if (context == nullptr) {
-        return;
-    }
-    sp<BLASTBufferQueue> bq = static_cast<BLASTBufferQueue*>(context);
-    bq->transactionCallback(latchTime, presentFence, stats);
+TransactionCompletedCallbackTakesContext BLASTBufferQueue::makeTransactionCallbackThunk() {
+    return [bbq = sp<BLASTBufferQueue>::fromExisting(
+                    this)](void* /*context*/, nsecs_t latchTime, const sp<Fence>& presentFence,
+                           const std::vector<SurfaceControlStats>& stats) {
+        bbq->transactionCallback(latchTime, presentFence, stats);
+    };
 }
 
 void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
@@ -381,6 +405,7 @@
                                                     stat.latchTime,
                                                     stat.frameEventStats.dequeueReadyTime);
                 }
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
                 auto currFrameNumber = stat.frameEventStats.frameNumber;
                 std::vector<ReleaseCallbackId> staleReleases;
                 for (const auto& [key, value]: mSubmitted) {
@@ -396,6 +421,7 @@
                                                 stat.currentMaxAcquiredBufferCount,
                                                 true /* fakeRelease */);
                 }
+#endif
             } else {
                 BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
             }
@@ -403,8 +429,15 @@
             BQA_LOGE("No matching SurfaceControls found: mSurfaceControlsWithPendingCallback was "
                      "empty.");
         }
+    }
+}
 
-        decStrong((void*)transactionCallbackThunk);
+void BLASTBufferQueue::flushShadowQueue() {
+    BQA_LOGV("flushShadowQueue");
+    int32_t numFramesToFlush = mNumFrameAvailable;
+    while (numFramesToFlush > 0) {
+        acquireNextBufferLocked(std::nullopt);
+        numFramesToFlush--;
     }
 }
 
@@ -412,24 +445,17 @@
 // BBQ. This is because if the BBQ is destroyed, then the buffers will be released by the client.
 // So we pass in a weak pointer to the BBQ and if it still alive, then we release the buffer.
 // Otherwise, this is a no-op.
-static void releaseBufferCallbackThunk(wp<BLASTBufferQueue> context, const ReleaseCallbackId& id,
-                                       const sp<Fence>& releaseFence,
-                                       std::optional<uint32_t> currentMaxAcquiredBufferCount) {
-    sp<BLASTBufferQueue> blastBufferQueue = context.promote();
-    if (blastBufferQueue) {
-        blastBufferQueue->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount);
-    } else {
-        ALOGV("releaseBufferCallbackThunk %s blastBufferQueue is dead", id.to_string().c_str());
-    }
-}
-
-void BLASTBufferQueue::flushShadowQueue() {
-    BQA_LOGV("flushShadowQueue");
-    int numFramesToFlush = mNumFrameAvailable;
-    while (numFramesToFlush > 0) {
-        acquireNextBufferLocked(std::nullopt);
-        numFramesToFlush--;
-    }
+ReleaseBufferCallback BLASTBufferQueue::makeReleaseBufferCallbackThunk() {
+    return [weakBbq = wp<BLASTBufferQueue>::fromExisting(
+                    this)](const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+                           std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+        sp<BLASTBufferQueue> bbq = weakBbq.promote();
+        if (!bbq) {
+            ALOGV("releaseBufferCallbackThunk %s blastBufferQueue is dead", id.to_string().c_str());
+            return;
+        }
+        bbq->releaseBufferCallback(id, releaseFence, currentMaxAcquiredBufferCount);
+    };
 }
 
 void BLASTBufferQueue::releaseBufferCallback(
@@ -594,9 +620,6 @@
         t->notifyProducerDisconnect(mSurfaceControl);
     }
 
-    // Ensure BLASTBufferQueue stays alive until we receive the transaction complete callback.
-    incStrong((void*)transactionCallbackThunk);
-
     // 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.
@@ -609,16 +632,30 @@
                            bufferItem.mGraphicBuffer->getHeight(), bufferItem.mTransform,
                            bufferItem.mScalingMode, crop);
 
-    auto releaseBufferCallback =
-            std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
-                      std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+    ReleaseBufferCallback releaseBufferCallback =
+            applyTransaction ? EMPTY_RELEASE_CALLBACK : makeReleaseBufferCallbackThunk();
+#else
+    auto releaseBufferCallback = makeReleaseBufferCallbackThunk();
+#endif
     sp<Fence> fence = bufferItem.mFence ? new Fence(bufferItem.mFence->dup()) : Fence::NO_FENCE;
+
+    nsecs_t dequeueTime = -1;
+    {
+        std::lock_guard _lock{mTimestampMutex};
+        auto dequeueTimeIt = mDequeueTimestamps.find(buffer->getId());
+        if (dequeueTimeIt != mDequeueTimestamps.end()) {
+            dequeueTime = dequeueTimeIt->second;
+            mDequeueTimestamps.erase(dequeueTimeIt);
+        }
+    }
+
     t->setBuffer(mSurfaceControl, buffer, fence, bufferItem.mFrameNumber, mProducerId,
-                 releaseBufferCallback);
+                 releaseBufferCallback, dequeueTime);
     t->setDataspace(mSurfaceControl, static_cast<ui::Dataspace>(bufferItem.mDataSpace));
     t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
     t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
-    t->addTransactionCompletedCallback(transactionCallbackThunk, static_cast<void*>(this));
+    t->addTransactionCompletedCallback(makeTransactionCallbackThunk(), nullptr);
 
     mSurfaceControlsWithPendingCallback.push(mSurfaceControl);
 
@@ -658,17 +695,6 @@
         mPendingFrameTimelines.pop();
     }
 
-    {
-        std::lock_guard _lock{mTimestampMutex};
-        auto dequeueTime = mDequeueTimestamps.find(buffer->getId());
-        if (dequeueTime != mDequeueTimestamps.end()) {
-            Parcel p;
-            p.writeInt64(dequeueTime->second);
-            t->setMetadata(mSurfaceControl, gui::METADATA_DEQUEUE_TIME, p);
-            mDequeueTimestamps.erase(dequeueTime);
-        }
-    }
-
     mergePendingTransactions(t, bufferItem.mFrameNumber);
     if (applyTransaction) {
         // All transactions on our apply token are one-way. See comment on mAppliedLastTransaction
@@ -786,9 +812,9 @@
 
             // Only need a commit callback when syncing to ensure the buffer that's synced has been
             // sent to SF
-            incStrong((void*)transactionCommittedCallbackThunk);
-            mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
-                                                              static_cast<void*>(this));
+            mSyncTransaction
+                    ->addTransactionCommittedCallback(makeTransactionCommittedCallbackThunk(),
+                                                      nullptr);
             if (mAcquireSingleBuffer) {
                 prevCallback = mTransactionReadyCallback;
                 prevTransaction = mSyncTransaction;
@@ -817,7 +843,7 @@
 void BLASTBufferQueue::onFrameCancelled(const uint64_t bufferId) {
     std::lock_guard _lock{mTimestampMutex};
     mDequeueTimestamps.erase(bufferId);
-};
+}
 
 bool BLASTBufferQueue::syncNextTransaction(
         std::function<void(SurfaceComposerClient::Transaction*)> callback,
@@ -1096,15 +1122,26 @@
         AsyncWorker::getInstance().post(
                 [listener = mListener, slots = slots]() { listener->onBuffersDiscarded(slots); });
     }
+
+    void onBufferDetached(int slot) override {
+        AsyncWorker::getInstance().post(
+                [listener = mListener, slot = slot]() { listener->onBufferDetached(slot); });
+    }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    void onBufferAttached() override {
+        AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferAttached(); });
+    }
+#endif
 };
 
 // Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls
 // can be non-blocking when the producer is in the client process.
 class BBQBufferQueueProducer : public BufferQueueProducer {
 public:
-    BBQBufferQueueProducer(const sp<BufferQueueCore>& core, wp<BLASTBufferQueue> bbq)
+    BBQBufferQueueProducer(const sp<BufferQueueCore>& core, const wp<BLASTBufferQueue>& bbq)
           : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/),
-            mBLASTBufferQueue(std::move(bbq)) {}
+            mBLASTBufferQueue(bbq) {}
 
     status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp,
                      QueueBufferOutput* output) override {
@@ -1119,27 +1156,32 @@
     // We want to resize the frame history when changing the size of the buffer queue
     status_t setMaxDequeuedBufferCount(int maxDequeuedBufferCount) override {
         int maxBufferCount;
-        status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount,
-                                                                         &maxBufferCount);
-        // if we can't determine the max buffer count, then just skip growing the history size
-        if (status == OK) {
-            size_t newFrameHistorySize = maxBufferCount + 2; // +2 because triple buffer rendering
-            // optimize away resizing the frame history unless it will grow
-            if (newFrameHistorySize > FrameEventHistory::INITIAL_MAX_FRAME_HISTORY) {
-                sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
-                if (bbq != nullptr) {
-                    ALOGV("increasing frame history size to %zu", newFrameHistorySize);
-                    bbq->resizeFrameEventHistory(newFrameHistorySize);
-                }
-            }
+        if (status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount,
+                                                                             &maxBufferCount);
+            status != OK) {
+            return status;
         }
-        return status;
+
+        sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+        if (!bbq) {
+            return OK;
+        }
+
+        // if we can't determine the max buffer count, then just skip growing the history size
+        size_t newFrameHistorySize = maxBufferCount + 2; // +2 because triple buffer rendering
+        // optimize away resizing the frame history unless it will grow
+        if (newFrameHistorySize > FrameEventHistory::INITIAL_MAX_FRAME_HISTORY) {
+            ALOGV("increasing frame history size to %zu", newFrameHistorySize);
+            bbq->resizeFrameEventHistory(newFrameHistorySize);
+        }
+
+        return OK;
     }
 
     int query(int what, int* value) override {
         if (what == NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER) {
             *value = 1;
-            return NO_ERROR;
+            return OK;
         }
         return BufferQueueProducer::query(what, value);
     }
@@ -1219,7 +1261,125 @@
 void BLASTBufferQueue::setTransactionHangCallback(
         std::function<void(const std::string&)> callback) {
     std::lock_guard _lock{mMutex};
-    mTransactionHangCallback = callback;
+    mTransactionHangCallback = std::move(callback);
 }
 
+void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) {
+    std::lock_guard _lock{mMutex};
+    mApplyToken = std::move(applyToken);
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+
+BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
+        std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> endpoint)
+      : mEndpoint{std::move(endpoint)} {
+    mEpollFd = android::base::unique_fd{epoll_create1(0)};
+    LOG_ALWAYS_FATAL_IF(!mEpollFd.ok(),
+                        "Failed to create buffer release epoll file descriptor. errno=%d "
+                        "message='%s'",
+                        errno, strerror(errno));
+
+    epoll_event registerEndpointFd{};
+    registerEndpointFd.events = EPOLLIN;
+    registerEndpointFd.data.fd = mEndpoint->getFd();
+    status_t status =
+            epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEndpoint->getFd(), &registerEndpointFd);
+    LOG_ALWAYS_FATAL_IF(status == -1,
+                        "Failed to register buffer release consumer file descriptor with epoll. "
+                        "errno=%d message='%s'",
+                        errno, strerror(errno));
+
+    mEventFd = android::base::unique_fd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK));
+    LOG_ALWAYS_FATAL_IF(!mEventFd.ok(),
+                        "Failed to create buffer release event file descriptor. errno=%d "
+                        "message='%s'",
+                        errno, strerror(errno));
+
+    epoll_event registerEventFd{};
+    registerEventFd.events = EPOLLIN;
+    registerEventFd.data.fd = mEventFd.get();
+    status = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mEventFd.get(), &registerEventFd);
+    LOG_ALWAYS_FATAL_IF(status == -1,
+                        "Failed to register buffer release event file descriptor with epoll. "
+                        "errno=%d message='%s'",
+                        errno, strerror(errno));
+}
+
+BLASTBufferQueue::BufferReleaseReader& BLASTBufferQueue::BufferReleaseReader::operator=(
+        BufferReleaseReader&& other) {
+    if (this != &other) {
+        ftl::FakeGuard guard{mMutex};
+        ftl::FakeGuard otherGuard{other.mMutex};
+        mEndpoint = std::move(other.mEndpoint);
+        mEpollFd = std::move(other.mEpollFd);
+        mEventFd = std::move(other.mEventFd);
+    }
+    return *this;
+}
+
+status_t BLASTBufferQueue::BufferReleaseReader::readBlocking(ReleaseCallbackId& outId,
+                                                             sp<Fence>& outFence,
+                                                             uint32_t& outMaxAcquiredBufferCount) {
+    epoll_event event{};
+    while (true) {
+        int eventCount = epoll_wait(mEpollFd.get(), &event, 1 /* maxevents */, -1 /* timeout */);
+        if (eventCount == 1) {
+            break;
+        }
+        if (eventCount == -1 && errno != EINTR) {
+            ALOGE("epoll_wait error while waiting for buffer release. errno=%d message='%s'", errno,
+                  strerror(errno));
+        }
+    }
+
+    if (event.data.fd == mEventFd.get()) {
+        uint64_t value;
+        if (read(mEventFd.get(), &value, sizeof(uint64_t)) == -1 && errno != EWOULDBLOCK) {
+            ALOGE("error while reading from eventfd. errno=%d message='%s'", errno,
+                  strerror(errno));
+        }
+        return WOULD_BLOCK;
+    }
+
+    std::lock_guard lock{mMutex};
+    return mEndpoint->readReleaseFence(outId, outFence, outMaxAcquiredBufferCount);
+}
+
+void BLASTBufferQueue::BufferReleaseReader::interruptBlockingRead() {
+    uint64_t value = 1;
+    if (write(mEventFd.get(), &value, sizeof(uint64_t)) == -1) {
+        ALOGE("failed to notify dequeue event. errno=%d message='%s'", errno, strerror(errno));
+    }
+}
+
+void BLASTBufferQueue::BufferReleaseThread::start(const sp<BLASTBufferQueue>& bbq) {
+    mRunning = std::make_shared<std::atomic_bool>(true);
+    mReader = bbq->mBufferReleaseReader;
+    std::thread([running = mRunning, reader = mReader, weakBbq = wp<BLASTBufferQueue>(bbq)]() {
+        pthread_setname_np(pthread_self(), "BufferReleaseThread");
+        while (*running) {
+            ReleaseCallbackId id;
+            sp<Fence> fence;
+            uint32_t maxAcquiredBufferCount;
+            if (status_t status = reader->readBlocking(id, fence, maxAcquiredBufferCount);
+                status != OK) {
+                continue;
+            }
+            sp<BLASTBufferQueue> bbq = weakBbq.promote();
+            if (!bbq) {
+                return;
+            }
+            bbq->releaseBufferCallback(id, fence, maxAcquiredBufferCount);
+        }
+    }).detach();
+}
+
+BLASTBufferQueue::BufferReleaseThread::~BufferReleaseThread() {
+    *mRunning = false;
+    mReader->interruptBlockingRead();
+}
+
+#endif
+
 } // namespace android
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index e6331e7..bfe3d6e 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -21,8 +21,11 @@
 
 #include <inttypes.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
+#include <ui/BufferQueueDefs.h>
+#include <ui/GraphicBuffer.h>
 
 #define BI_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
 // #define BI_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
@@ -32,18 +35,37 @@
 
 namespace android {
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+BufferItemConsumer::BufferItemConsumer(uint64_t consumerUsage, int bufferCount,
+                                       bool controlledByApp, bool isConsumerSurfaceFlinger)
+      : ConsumerBase(controlledByApp, isConsumerSurfaceFlinger) {
+    initialize(consumerUsage, bufferCount);
+}
+
+BufferItemConsumer::BufferItemConsumer(const sp<IGraphicBufferProducer>& producer,
+                                       const sp<IGraphicBufferConsumer>& consumer,
+                                       uint64_t consumerUsage, int bufferCount,
+                                       bool controlledByApp)
+      : ConsumerBase(producer, consumer, controlledByApp) {
+    initialize(consumerUsage, bufferCount);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 BufferItemConsumer::BufferItemConsumer(
         const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
         int bufferCount, bool controlledByApp) :
     ConsumerBase(consumer, controlledByApp)
 {
+    initialize(consumerUsage, bufferCount);
+}
+
+void BufferItemConsumer::initialize(uint64_t consumerUsage, int bufferCount) {
     status_t err = mConsumer->setConsumerUsageBits(consumerUsage);
-    LOG_ALWAYS_FATAL_IF(err != OK,
-            "Failed to set consumer usage bits to %#" PRIx64, consumerUsage);
+    LOG_ALWAYS_FATAL_IF(err != OK, "Failed to set consumer usage bits to %#" PRIx64, consumerUsage);
     if (bufferCount != DEFAULT_MAX_BUFFERS) {
         err = mConsumer->setMaxAcquiredBufferCount(bufferCount);
-        LOG_ALWAYS_FATAL_IF(err != OK,
-                "Failed to set max acquired buffer count to %d", bufferCount);
+        LOG_ALWAYS_FATAL_IF(err != OK, "Failed to set max acquired buffer count to %d",
+                            bufferCount);
     }
 }
 
@@ -87,17 +109,38 @@
 
 status_t BufferItemConsumer::releaseBuffer(const BufferItem &item,
         const sp<Fence>& releaseFence) {
-    status_t err;
+    Mutex::Autolock _l(mMutex);
+    return releaseBufferSlotLocked(item.mSlot, item.mGraphicBuffer, releaseFence);
+}
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+status_t BufferItemConsumer::releaseBuffer(const sp<GraphicBuffer>& buffer,
+                                           const sp<Fence>& releaseFence) {
     Mutex::Autolock _l(mMutex);
 
-    err = addReleaseFenceLocked(item.mSlot, item.mGraphicBuffer, releaseFence);
+    if (buffer == nullptr) {
+        return BAD_VALUE;
+    }
+
+    int slotIndex = getSlotForBufferLocked(buffer);
+    if (slotIndex == INVALID_BUFFER_SLOT) {
+        return BAD_VALUE;
+    }
+
+    return releaseBufferSlotLocked(slotIndex, buffer, releaseFence);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+status_t BufferItemConsumer::releaseBufferSlotLocked(int slotIndex, const sp<GraphicBuffer>& buffer,
+                                                     const sp<Fence>& releaseFence) {
+    status_t err;
+
+    err = addReleaseFenceLocked(slotIndex, buffer, releaseFence);
     if (err != OK) {
         BI_LOGE("Failed to addReleaseFenceLocked");
     }
 
-    err = releaseBufferLocked(item.mSlot, item.mGraphicBuffer, EGL_NO_DISPLAY,
-            EGL_NO_SYNC_KHR);
+    err = releaseBufferLocked(slotIndex, buffer, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR);
     if (err != OK && err != IGraphicBufferConsumer::STALE_BUFFER_SLOT) {
         BI_LOGE("Failed to release buffer: %s (%d)",
                 strerror(-err), err);
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 88d456b..831b2ee 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -45,7 +45,10 @@
 
 #include <system/window.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
+using namespace com::android::graphics::libgui;
 
 // Macros for include BufferQueueCore information in log messages
 #define BQ_LOGV(x, ...)                                                                           \
@@ -423,6 +426,11 @@
     sp<IConsumerListener> listener;
     bool callOnFrameDequeued = false;
     uint64_t bufferId = 0; // Only used if callOnFrameDequeued == true
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    std::vector<gui::AdditionalOptions> allocOptions;
+    uint32_t allocOptionsGenId = 0;
+#endif
+
     { // Autolock scope
         std::unique_lock<std::mutex> lock(mCore->mMutex);
 
@@ -486,11 +494,17 @@
         }
 
         const sp<GraphicBuffer>& buffer(mSlots[found].mGraphicBuffer);
-        if (mCore->mSharedBufferSlot == found &&
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage)) {
-            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared"
-                    "buffer");
 
+        bool needsReallocation = buffer == nullptr ||
+                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        needsReallocation |= mSlots[found].mAdditionalOptionsGenerationId !=
+                mCore->mAdditionalOptionsGenerationId;
+#endif
+
+        if (mCore->mSharedBufferSlot == found && needsReallocation) {
+            BQ_LOGE("dequeueBuffer: cannot re-allocate a shared buffer");
             return BAD_VALUE;
         }
 
@@ -505,9 +519,7 @@
 
         mSlots[found].mBufferState.dequeue();
 
-        if ((buffer == nullptr) ||
-                buffer->needsReallocation(width, height, format, BQ_LAYER_COUNT, usage))
-        {
+        if (needsReallocation) {
             if (CC_UNLIKELY(ATRACE_ENABLED())) {
                 if (buffer == nullptr) {
                     ATRACE_FORMAT_INSTANT("%s buffer reallocation: null", mConsumerName.c_str());
@@ -530,6 +542,10 @@
             mSlots[found].mFence = Fence::NO_FENCE;
             mCore->mBufferAge = 0;
             mCore->mIsAllocating = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
 
             returnFlags |= BUFFER_NEEDS_REALLOCATION;
         } else {
@@ -575,9 +591,29 @@
 
     if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
         BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = width,
+                .height = height,
+                .format = format,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = usage,
+                .requestorName = {mConsumerName.c_str(), mConsumerName.size()},
+                .extras = std::move(tempOptions),
+        };
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
         sp<GraphicBuffer> graphicBuffer =
                 new GraphicBuffer(width, height, format, BQ_LAYER_COUNT, usage,
                                   {mConsumerName.c_str(), mConsumerName.size()});
+#endif
 
         status_t error = graphicBuffer->initCheck();
 
@@ -587,6 +623,9 @@
             if (error == NO_ERROR && !mCore->mIsAbandoned) {
                 graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
                 mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*outSlot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
                 callOnFrameDequeued = true;
                 bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
             }
@@ -888,6 +927,7 @@
     uint64_t currentFrameNumber = 0;
     BufferItem item;
     int connectedApi;
+    bool enableEglCpuThrottling = true;
     sp<Fence> lastQueuedFence;
 
     { // Autolock scope
@@ -1061,6 +1101,9 @@
         VALIDATE_CONSISTENCY();
 
         connectedApi = mCore->mConnectedApi;
+        if (flags::bq_producer_throttles_only_async_mode()) {
+            enableEglCpuThrottling = mCore->mAsyncMode || mCore->mDequeueBufferCannotBlock;
+        }
         lastQueuedFence = std::move(mLastQueueBufferFence);
 
         mLastQueueBufferFence = std::move(acquireFence);
@@ -1106,7 +1149,7 @@
     }
 
     // Wait without lock held
-    if (connectedApi == NATIVE_WINDOW_API_EGL) {
+    if (connectedApi == NATIVE_WINDOW_API_EGL && enableEglCpuThrottling) {
         // Waiting here allows for two full buffers to be queued but not a
         // third. In the event that frames take varying time, this makes a
         // small trade-off in favor of latency rather than throughput.
@@ -1345,6 +1388,9 @@
     }
 
     mCore->mAllowAllocation = true;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    mCore->mAdditionalOptions.clear();
+#endif
     VALIDATE_CONSISTENCY();
     return status;
 }
@@ -1413,6 +1459,9 @@
                     mCore->mSidebandStream.clear();
                     mCore->mDequeueCondition.notify_all();
                     mCore->mAutoPrerotation = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                    mCore->mAdditionalOptions.clear();
+#endif
                     listener = mCore->mConsumerListener;
                 } else if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
                     BQ_LOGE("disconnect: not connected (req=%d)", api);
@@ -1459,12 +1508,15 @@
 
     const bool useDefaultSize = !width && !height;
     while (true) {
-        size_t newBufferCount = 0;
         uint32_t allocWidth = 0;
         uint32_t allocHeight = 0;
         PixelFormat allocFormat = PIXEL_FORMAT_UNKNOWN;
         uint64_t allocUsage = 0;
         std::string allocName;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<gui::AdditionalOptions> allocOptions;
+        uint32_t allocOptionsGenId = 0;
+#endif
         { // Autolock scope
             std::unique_lock<std::mutex> lock(mCore->mMutex);
             mCore->waitWhileAllocatingLocked(lock);
@@ -1477,8 +1529,9 @@
 
             // Only allocate one buffer at a time to reduce risks of overlapping an allocation from
             // both allocateBuffers and dequeueBuffer.
-            newBufferCount = mCore->mFreeSlots.empty() ? 0 : 1;
-            if (newBufferCount == 0) {
+            if (mCore->mFreeSlots.empty()) {
+                BQ_LOGV("allocateBuffers: a slot was occupied while "
+                        "allocating. Dropping allocated buffer.");
                 return;
             }
 
@@ -1493,26 +1546,50 @@
             allocUsage = usage | mCore->mConsumerUsageBits;
             allocName.assign(mCore->mConsumerName.c_str(), mCore->mConsumerName.size());
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptions = mCore->mAdditionalOptions;
+            allocOptionsGenId = mCore->mAdditionalOptionsGenerationId;
+#endif
+
             mCore->mIsAllocating = true;
+
         } // Autolock scope
 
-        Vector<sp<GraphicBuffer>> buffers;
-        for (size_t i = 0; i < newBufferCount; ++i) {
-            sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
-                    allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
-                    allocUsage, allocName);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        std::vector<GraphicBufferAllocator::AdditionalOptions> tempOptions;
+        tempOptions.reserve(allocOptions.size());
+        for (const auto& it : allocOptions) {
+            tempOptions.emplace_back(it.name.c_str(), it.value);
+        }
+        const GraphicBufferAllocator::AllocationRequest allocRequest = {
+                .importBuffer = true,
+                .width = allocWidth,
+                .height = allocHeight,
+                .format = allocFormat,
+                .layerCount = BQ_LAYER_COUNT,
+                .usage = allocUsage,
+                .requestorName = allocName,
+                .extras = std::move(tempOptions),
+        };
+#endif
 
-            status_t result = graphicBuffer->initCheck();
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(allocRequest);
+#else
+        sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
+                allocWidth, allocHeight, allocFormat, BQ_LAYER_COUNT,
+                allocUsage, allocName);
+#endif
 
-            if (result != NO_ERROR) {
-                BQ_LOGE("allocateBuffers: failed to allocate buffer (%u x %u, format"
-                        " %u, usage %#" PRIx64 ")", width, height, format, usage);
-                std::lock_guard<std::mutex> lock(mCore->mMutex);
-                mCore->mIsAllocating = false;
-                mCore->mIsAllocatingCondition.notify_all();
-                return;
-            }
-            buffers.push_back(graphicBuffer);
+        status_t result = graphicBuffer->initCheck();
+
+        if (result != NO_ERROR) {
+            BQ_LOGE("allocateBuffers: failed to allocate buffer (%u x %u, format"
+                    " %u, usage %#" PRIx64 ")", width, height, format, usage);
+            std::lock_guard<std::mutex> lock(mCore->mMutex);
+            mCore->mIsAllocating = false;
+            mCore->mIsAllocatingCondition.notify_all();
+            return;
         }
 
         { // Autolock scope
@@ -1527,8 +1604,12 @@
             PixelFormat checkFormat = format != 0 ?
                     format : mCore->mDefaultBufferFormat;
             uint64_t checkUsage = usage | mCore->mConsumerUsageBits;
+            bool allocOptionsChanged = false;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+            allocOptionsChanged = allocOptionsGenId != mCore->mAdditionalOptionsGenerationId;
+#endif
             if (checkWidth != allocWidth || checkHeight != allocHeight ||
-                checkFormat != allocFormat || checkUsage != allocUsage) {
+                checkFormat != allocFormat || checkUsage != allocUsage || allocOptionsChanged) {
                 // Something changed while we released the lock. Retry.
                 BQ_LOGV("allocateBuffers: size/format/usage changed while allocating. Retrying.");
                 mCore->mIsAllocating = false;
@@ -1536,16 +1617,17 @@
                 continue;
             }
 
-            for (size_t i = 0; i < newBufferCount; ++i) {
-                if (mCore->mFreeSlots.empty()) {
-                    BQ_LOGV("allocateBuffers: a slot was occupied while "
-                            "allocating. Dropping allocated buffer.");
-                    continue;
-                }
+            if (mCore->mFreeSlots.empty()) {
+                BQ_LOGV("allocateBuffers: a slot was occupied while "
+                        "allocating. Dropping allocated buffer.");
+            } else {
                 auto slot = mCore->mFreeSlots.begin();
                 mCore->clearBufferSlotLocked(*slot); // Clean up the slot first
-                mSlots[*slot].mGraphicBuffer = buffers[i];
+                mSlots[*slot].mGraphicBuffer = graphicBuffer;
                 mSlots[*slot].mFence = Fence::NO_FENCE;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+                mSlots[*slot].mAdditionalOptionsGenerationId = allocOptionsGenId;
+#endif
 
                 // freeBufferLocked puts this slot on the free slots list. Since
                 // we then attached a buffer, move the slot to free buffer list.
@@ -1781,4 +1863,29 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t BufferQueueProducer::setAdditionalOptions(
+        const std::vector<gui::AdditionalOptions>& options) {
+    ATRACE_CALL();
+    BQ_LOGV("setAdditionalOptions, size = %zu", options.size());
+
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    std::lock_guard<std::mutex> lock(mCore->mMutex);
+
+    if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
+        BQ_LOGE("setAdditionalOptions: BufferQueue not connected, cannot set additional options");
+        return NO_INIT;
+    }
+
+    if (mCore->mAdditionalOptions != options) {
+        mCore->mAdditionalOptions = options;
+        mCore->mAdditionalOptionsGenerationId++;
+    }
+    return NO_ERROR;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/BufferReleaseChannel.cpp b/libs/gui/BufferReleaseChannel.cpp
new file mode 100644
index 0000000..27367aa
--- /dev/null
+++ b/libs/gui/BufferReleaseChannel.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BufferReleaseChannel"
+
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <android-base/result.h>
+#include <android/binder_status.h>
+#include <binder/Parcel.h>
+#include <utils/Flattenable.h>
+
+#include <gui/BufferReleaseChannel.h>
+#include <private/gui/ParcelUtils.h>
+
+using android::base::Result;
+
+namespace android::gui {
+
+namespace {
+
+template <typename T>
+static void readAligned(const void*& buffer, size_t& size, T& value) {
+    size -= FlattenableUtils::align<alignof(T)>(buffer);
+    FlattenableUtils::read(buffer, size, value);
+}
+
+template <typename T>
+static void writeAligned(void*& buffer, size_t& size, T value) {
+    size -= FlattenableUtils::align<alignof(T)>(buffer);
+    FlattenableUtils::write(buffer, size, value);
+}
+
+template <typename T>
+static void addAligned(size_t& size, T /* value */) {
+    size = FlattenableUtils::align<sizeof(T)>(size);
+    size += sizeof(T);
+}
+
+template <typename T>
+static inline constexpr uint32_t low32(const T n) {
+    return static_cast<uint32_t>(static_cast<uint64_t>(n));
+}
+
+template <typename T>
+static inline constexpr uint32_t high32(const T n) {
+    return static_cast<uint32_t>(static_cast<uint64_t>(n) >> 32);
+}
+
+template <typename T>
+static inline constexpr T to64(const uint32_t lo, const uint32_t hi) {
+    return static_cast<T>(static_cast<uint64_t>(hi) << 32 | lo);
+}
+
+} // namespace
+
+size_t BufferReleaseChannel::Message::getPodSize() const {
+    size_t size = 0;
+    addAligned(size, low32(releaseCallbackId.bufferId));
+    addAligned(size, high32(releaseCallbackId.bufferId));
+    addAligned(size, low32(releaseCallbackId.framenumber));
+    addAligned(size, high32(releaseCallbackId.framenumber));
+    addAligned(size, maxAcquiredBufferCount);
+    return size;
+}
+
+size_t BufferReleaseChannel::Message::getFlattenedSize() const {
+    size_t size = releaseFence->getFlattenedSize();
+    size = FlattenableUtils::align<4>(size);
+    size += getPodSize();
+    return size;
+}
+
+status_t BufferReleaseChannel::Message::flatten(void*& buffer, size_t& size, int*& fds,
+                                                size_t& count) const {
+    if (status_t err = releaseFence->flatten(buffer, size, fds, count); err != OK) {
+        return err;
+    }
+    size -= FlattenableUtils::align<4>(buffer);
+
+    // Check we still have enough space
+    if (size < getPodSize()) {
+        return NO_MEMORY;
+    }
+
+    writeAligned(buffer, size, low32(releaseCallbackId.bufferId));
+    writeAligned(buffer, size, high32(releaseCallbackId.bufferId));
+    writeAligned(buffer, size, low32(releaseCallbackId.framenumber));
+    writeAligned(buffer, size, high32(releaseCallbackId.framenumber));
+    writeAligned(buffer, size, maxAcquiredBufferCount);
+    return OK;
+}
+
+status_t BufferReleaseChannel::Message::unflatten(void const*& buffer, size_t& size,
+                                                  int const*& fds, size_t& count) {
+    releaseFence = new Fence();
+    if (status_t err = releaseFence->unflatten(buffer, size, fds, count); err != OK) {
+        return err;
+    }
+    size -= FlattenableUtils::align<4>(buffer);
+
+    // Check we still have enough space
+    if (size < getPodSize()) {
+        return OK;
+    }
+
+    uint32_t bufferIdLo = 0, bufferIdHi = 0;
+    uint32_t frameNumberLo = 0, frameNumberHi = 0;
+
+    readAligned(buffer, size, bufferIdLo);
+    readAligned(buffer, size, bufferIdHi);
+    releaseCallbackId.bufferId = to64<int64_t>(bufferIdLo, bufferIdHi);
+    readAligned(buffer, size, frameNumberLo);
+    readAligned(buffer, size, frameNumberHi);
+    releaseCallbackId.framenumber = to64<uint64_t>(frameNumberLo, frameNumberHi);
+    readAligned(buffer, size, maxAcquiredBufferCount);
+
+    return OK;
+}
+
+status_t BufferReleaseChannel::ConsumerEndpoint::readReleaseFence(
+        ReleaseCallbackId& outReleaseCallbackId, sp<Fence>& outReleaseFence,
+        uint32_t& outMaxAcquiredBufferCount) {
+    Message message;
+    mFlattenedBuffer.resize(message.getFlattenedSize());
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+
+    iovec iov{
+            .iov_base = mFlattenedBuffer.data(),
+            .iov_len = mFlattenedBuffer.size(),
+    };
+
+    msghdr msg{
+            .msg_iov = &iov,
+            .msg_iovlen = 1,
+            .msg_control = controlMessageBuffer.data(),
+            .msg_controllen = controlMessageBuffer.size(),
+    };
+
+    int result;
+    do {
+        result = recvmsg(mFd, &msg, 0);
+    } while (result == -1 && errno == EINTR);
+    if (result == -1) {
+        if (errno == EWOULDBLOCK || errno == EAGAIN) {
+            return WOULD_BLOCK;
+        }
+        ALOGE("Error reading release fence from socket: error %#x (%s)", errno, strerror(errno));
+        return UNKNOWN_ERROR;
+    }
+
+    if (msg.msg_iovlen != 1) {
+        ALOGE("Error reading release fence from socket: bad data length");
+        return UNKNOWN_ERROR;
+    }
+
+    if (msg.msg_controllen % sizeof(int) != 0) {
+        ALOGE("Error reading release fence from socket: bad fd length");
+        return UNKNOWN_ERROR;
+    }
+
+    size_t dataLen = msg.msg_iov->iov_len;
+    const void* data = static_cast<const void*>(msg.msg_iov->iov_base);
+    if (!data) {
+        ALOGE("Error reading release fence from socket: no buffer data");
+        return UNKNOWN_ERROR;
+    }
+
+    size_t fdCount = 0;
+    const int* fdData = nullptr;
+    if (cmsghdr* cmsg = CMSG_FIRSTHDR(&msg)) {
+        fdData = reinterpret_cast<const int*>(CMSG_DATA(cmsg));
+        fdCount = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+    }
+
+    if (status_t err = message.unflatten(data, dataLen, fdData, fdCount); err != OK) {
+        return err;
+    }
+
+    outReleaseCallbackId = message.releaseCallbackId;
+    outReleaseFence = std::move(message.releaseFence);
+    outMaxAcquiredBufferCount = message.maxAcquiredBufferCount;
+
+    return OK;
+}
+
+int BufferReleaseChannel::ProducerEndpoint::writeReleaseFence(const ReleaseCallbackId& callbackId,
+                                                              const sp<Fence>& fence,
+                                                              uint32_t maxAcquiredBufferCount) {
+    Message message{callbackId, fence ? fence : Fence::NO_FENCE, maxAcquiredBufferCount};
+    mFlattenedBuffer.resize(message.getFlattenedSize());
+    int flattenedFd;
+    {
+        // Make copies of needed items since flatten modifies them, and we don't
+        // want to send anything if there's an error during flatten.
+        void* flattenedBufferPtr = mFlattenedBuffer.data();
+        size_t flattenedBufferSize = mFlattenedBuffer.size();
+        int* flattenedFdPtr = &flattenedFd;
+        size_t flattenedFdCount = 1;
+        if (status_t err = message.flatten(flattenedBufferPtr, flattenedBufferSize, flattenedFdPtr,
+                                           flattenedFdCount);
+            err != OK) {
+            ALOGE("Failed to flatten BufferReleaseChannel message.");
+            return err;
+        }
+    }
+
+    iovec iov{
+            .iov_base = mFlattenedBuffer.data(),
+            .iov_len = mFlattenedBuffer.size(),
+    };
+
+    msghdr msg{
+            .msg_iov = &iov,
+            .msg_iovlen = 1,
+    };
+
+    std::array<uint8_t, CMSG_SPACE(sizeof(int))> controlMessageBuffer;
+    if (fence && fence->isValid()) {
+        msg.msg_control = controlMessageBuffer.data();
+        msg.msg_controllen = controlMessageBuffer.size();
+
+        cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+        cmsg->cmsg_level = SOL_SOCKET;
+        cmsg->cmsg_type = SCM_RIGHTS;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+        memcpy(CMSG_DATA(cmsg), &flattenedFd, sizeof(int));
+    }
+
+    int result;
+    do {
+        result = sendmsg(mFd, &msg, 0);
+    } while (result == -1 && errno == EINTR);
+    if (result == -1) {
+        ALOGD("Error writing release fence to socket: error %#x (%s)", errno, strerror(errno));
+        return -errno;
+    }
+
+    return OK;
+}
+
+status_t BufferReleaseChannel::ProducerEndpoint::readFromParcel(const android::Parcel* parcel) {
+    if (!parcel) return STATUS_BAD_VALUE;
+    SAFE_PARCEL(parcel->readUtf8FromUtf16, &mName);
+    SAFE_PARCEL(parcel->readUniqueFileDescriptor, &mFd);
+    return STATUS_OK;
+}
+
+status_t BufferReleaseChannel::ProducerEndpoint::writeToParcel(android::Parcel* parcel) const {
+    if (!parcel) return STATUS_BAD_VALUE;
+    SAFE_PARCEL(parcel->writeUtf8AsUtf16, mName);
+    SAFE_PARCEL(parcel->writeUniqueFileDescriptor, mFd);
+    return STATUS_OK;
+}
+
+status_t BufferReleaseChannel::open(std::string name,
+                                    std::unique_ptr<ConsumerEndpoint>& outConsumer,
+                                    std::shared_ptr<ProducerEndpoint>& outProducer) {
+    outConsumer.reset();
+    outProducer.reset();
+
+    int sockets[2];
+    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
+        ALOGE("[%s] Failed to create socket pair. errorno=%d message='%s'", name.c_str(), errno,
+              strerror(errno));
+        return -errno;
+    }
+
+    android::base::unique_fd consumerFd(sockets[0]);
+    android::base::unique_fd producerFd(sockets[1]);
+
+    // Socket buffer size. The default is typically about 128KB, which is much larger than
+    // we really need.
+    size_t bufferSize = 32 * 1024;
+    if (setsockopt(consumerFd.get(), SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set consumer socket send buffer size. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(consumerFd.get(), SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set consumer socket receive buffer size. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set producer socket send buffer size. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize)) ==
+        -1) {
+        ALOGE("[%s] Failed to set producer socket receive buffer size. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Configure the consumer socket to be non-blocking.
+    int flags = fcntl(consumerFd.get(), F_GETFL, 0);
+    if (flags == -1) {
+        ALOGE("[%s] Failed to get consumer socket flags. errno=%d message='%s'", name.c_str(),
+              errno, strerror(errno));
+        return -errno;
+    }
+    if (fcntl(consumerFd.get(), F_SETFL, flags | O_NONBLOCK) == -1) {
+        ALOGE("[%s] Failed to set consumer socket to non-blocking mode. errno=%d "
+              "message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Configure a timeout for the producer socket.
+    const timeval timeout{.tv_sec = 1, .tv_usec = 0};
+    if (setsockopt(producerFd.get(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeval)) == -1) {
+        ALOGE("[%s] Failed to set producer socket timeout. errno=%d message='%s'", name.c_str(),
+              errno, strerror(errno));
+        return -errno;
+    }
+
+    // Make the consumer read-only
+    if (shutdown(consumerFd.get(), SHUT_WR) == -1) {
+        ALOGE("[%s] Failed to shutdown writing on consumer socket. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    // Make the producer write-only
+    if (shutdown(producerFd.get(), SHUT_RD) == -1) {
+        ALOGE("[%s] Failed to shutdown reading on producer socket. errno=%d message='%s'",
+              name.c_str(), errno, strerror(errno));
+        return -errno;
+    }
+
+    outConsumer = std::make_unique<ConsumerEndpoint>(name, std::move(consumerFd));
+    outProducer = std::make_shared<ProducerEndpoint>(std::move(name), std::move(producerFd));
+    return STATUS_OK;
+}
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
index 4518b67..0c8f3fa 100644
--- a/libs/gui/Choreographer.cpp
+++ b/libs/gui/Choreographer.cpp
@@ -143,9 +143,9 @@
 void Choreographer::postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                              AChoreographer_frameCallback64 cb64,
                                              AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                             nsecs_t delay) {
+                                             nsecs_t delay, CallbackType callbackType) {
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay};
+    FrameCallback callback{cb, cb64, vsyncCallback, data, now + delay, callbackType};
     {
         std::lock_guard<std::mutex> _l{mLock};
         mFrameCallbacks.push(callback);
@@ -285,18 +285,8 @@
     }
 }
 
-void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
-                                  VsyncEventData vsyncEventData) {
-    std::vector<FrameCallback> callbacks{};
-    {
-        std::lock_guard<std::mutex> _l{mLock};
-        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
-        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
-            callbacks.push_back(mFrameCallbacks.top());
-            mFrameCallbacks.pop();
-        }
-    }
-    mLastVsyncEventData = vsyncEventData;
+void Choreographer::dispatchCallbacks(const std::vector<FrameCallback>& callbacks,
+                                      VsyncEventData vsyncEventData, nsecs_t timestamp) {
     for (const auto& cb : callbacks) {
         if (cb.vsyncCallback != nullptr) {
             ATRACE_FORMAT("AChoreographer_vsyncCallback %" PRId64,
@@ -319,6 +309,34 @@
     }
 }
 
+void Choreographer::dispatchVsync(nsecs_t timestamp, PhysicalDisplayId, uint32_t,
+                                  VsyncEventData vsyncEventData) {
+    std::vector<FrameCallback> animationCallbacks{};
+    std::vector<FrameCallback> inputCallbacks{};
+    {
+        std::lock_guard<std::mutex> _l{mLock};
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        while (!mFrameCallbacks.empty() && mFrameCallbacks.top().dueTime < now) {
+            if (mFrameCallbacks.top().callbackType == CALLBACK_INPUT) {
+                inputCallbacks.push_back(mFrameCallbacks.top());
+            } else {
+                animationCallbacks.push_back(mFrameCallbacks.top());
+            }
+            mFrameCallbacks.pop();
+        }
+    }
+    mLastVsyncEventData = vsyncEventData;
+    // Callbacks with type CALLBACK_INPUT should always run first
+    {
+        ATRACE_FORMAT("CALLBACK_INPUT");
+        dispatchCallbacks(inputCallbacks, vsyncEventData, timestamp);
+    }
+    {
+        ATRACE_FORMAT("CALLBACK_ANIMATION");
+        dispatchCallbacks(animationCallbacks, vsyncEventData, timestamp);
+    }
+}
+
 void Choreographer::dispatchHotplug(nsecs_t, PhysicalDisplayId displayId, bool connected) {
     ALOGV("choreographer %p ~ received hotplug event (displayId=%s, connected=%s), ignoring.", this,
           to_string(displayId).c_str(), toString(connected));
@@ -407,4 +425,8 @@
     return iter->second;
 }
 
+const sp<Looper> Choreographer::getLooper() {
+    return mLooper;
+}
+
 } // namespace android
diff --git a/libs/gui/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index b625c3f..602bba8 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#include <inttypes.h>
-
 #define LOG_TAG "ConsumerBase"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 //#define LOG_NDEBUG 0
@@ -29,17 +27,23 @@
 
 #include <cutils/atomic.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
+#include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
 
 #include <private/gui/ComposerService.h>
 
+#include <log/log.h>
 #include <utils/Log.h>
 #include <utils/String8.h>
 #include <utils/Trace.h>
 
+#include <inttypes.h>
+
 // Macros for including the ConsumerBase name in log messages
 #define CB_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
 // #define CB_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
@@ -59,6 +63,30 @@
         mAbandoned(false),
         mConsumer(bufferQueue),
         mPrevFinalReleaseFence(Fence::NO_FENCE) {
+    initialize(controlledByApp);
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+ConsumerBase::ConsumerBase(bool controlledByApp, bool consumerIsSurfaceFlinger)
+      : mAbandoned(false), mPrevFinalReleaseFence(Fence::NO_FENCE) {
+    sp<IGraphicBufferProducer> producer;
+    BufferQueue::createBufferQueue(&producer, &mConsumer, consumerIsSurfaceFlinger);
+    mSurface = sp<Surface>::make(producer, controlledByApp);
+    initialize(controlledByApp);
+}
+
+ConsumerBase::ConsumerBase(const sp<IGraphicBufferProducer>& producer,
+                           const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp)
+      : mAbandoned(false),
+        mConsumer(consumer),
+        mSurface(sp<Surface>::make(producer, controlledByApp)),
+        mPrevFinalReleaseFence(Fence::NO_FENCE) {
+    initialize(controlledByApp);
+}
+
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
+void ConsumerBase::initialize(bool controlledByApp) {
     // Choose a name using the PID and a process-unique ID.
     mName = String8::format("unnamed-%d-%d", getpid(), createProcessUniqueId());
 
@@ -96,6 +124,35 @@
     abandon();
 }
 
+int ConsumerBase::getSlotForBufferLocked(const sp<GraphicBuffer>& buffer) {
+    if (!buffer) {
+        return BufferQueue::INVALID_BUFFER_SLOT;
+    }
+
+    uint64_t id = buffer->getId();
+    for (int i = 0; i < BufferQueueDefs::NUM_BUFFER_SLOTS; i++) {
+        auto& slot = mSlots[i];
+        if (slot.mGraphicBuffer && slot.mGraphicBuffer->getId() == id) {
+            return i;
+        }
+    }
+
+    return BufferQueue::INVALID_BUFFER_SLOT;
+}
+
+status_t ConsumerBase::detachBufferLocked(int slotIndex) {
+    status_t result = mConsumer->detachBuffer(slotIndex);
+
+    if (result != NO_ERROR) {
+        CB_LOGE("Failed to detach buffer: %d", result);
+        return result;
+    }
+
+    freeBufferLocked(slotIndex);
+
+    return result;
+}
+
 void ConsumerBase::freeBufferLocked(int slotIndex) {
     CB_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
     mSlots[slotIndex].mGraphicBuffer = nullptr;
@@ -252,16 +309,30 @@
         return NO_INIT;
     }
 
-    status_t result = mConsumer->detachBuffer(slot);
-    if (result != NO_ERROR) {
-        CB_LOGE("Failed to detach buffer: %d", result);
-        return result;
+    return detachBufferLocked(slot);
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+status_t ConsumerBase::detachBuffer(const sp<GraphicBuffer>& buffer) {
+    CB_LOGV("detachBuffer");
+    Mutex::Autolock lock(mMutex);
+
+    if (mAbandoned) {
+        CB_LOGE("detachBuffer: ConsumerBase is abandoned!");
+        return NO_INIT;
+    }
+    if (buffer == nullptr) {
+        return BAD_VALUE;
     }
 
-    freeBufferLocked(slot);
+    int slotIndex = getSlotForBufferLocked(buffer);
+    if (slotIndex == BufferQueue::INVALID_BUFFER_SLOT) {
+        return BAD_VALUE;
+    }
 
-    return result;
+    return detachBufferLocked(slotIndex);
 }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
 status_t ConsumerBase::setDefaultBufferSize(uint32_t width, uint32_t height) {
     Mutex::Autolock _l(mMutex);
@@ -309,6 +380,17 @@
     return mConsumer->setTransformHint(hint);
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+status_t ConsumerBase::setMaxBufferCount(int bufferCount) {
+    Mutex::Autolock lock(mMutex);
+    if (mAbandoned) {
+        CB_LOGE("setMaxBufferCount: ConsumerBase is abandoned!");
+        return NO_INIT;
+    }
+    return mConsumer->setMaxBufferCount(bufferCount);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 status_t ConsumerBase::setMaxAcquiredBufferCount(int maxAcquiredBuffers) {
     Mutex::Autolock lock(mMutex);
     if (mAbandoned) {
@@ -318,6 +400,17 @@
     return mConsumer->setMaxAcquiredBufferCount(maxAcquiredBuffers);
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+status_t ConsumerBase::setConsumerIsProtected(bool isProtected) {
+    Mutex::Autolock lock(mMutex);
+    if (mAbandoned) {
+        CB_LOGE("setConsumerIsProtected: ConsumerBase is abandoned!");
+        return NO_INIT;
+    }
+    return mConsumer->setConsumerIsProtected(isProtected);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 sp<NativeHandle> ConsumerBase::getSidebandStream() const {
     Mutex::Autolock _l(mMutex);
     if (mAbandoned) {
@@ -384,6 +477,19 @@
     }
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+sp<Surface> ConsumerBase::getSurface() const {
+    LOG_ALWAYS_FATAL_IF(mSurface == nullptr,
+                        "It's illegal to get the surface of a Consumer that does not own it. This "
+                        "should be impossible once the old CTOR is removed.");
+    return mSurface;
+}
+
+sp<IGraphicBufferConsumer> ConsumerBase::getIGraphicBufferConsumer() const {
+    return mConsumer;
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 status_t ConsumerBase::acquireBufferLocked(BufferItem *item,
         nsecs_t presentWhen, uint64_t maxFrameNumber) {
     if (mAbandoned) {
diff --git a/libs/gui/CpuConsumer.cpp b/libs/gui/CpuConsumer.cpp
index 3031fa1..23b432e 100644
--- a/libs/gui/CpuConsumer.cpp
+++ b/libs/gui/CpuConsumer.cpp
@@ -18,9 +18,9 @@
 #define LOG_TAG "CpuConsumer"
 //#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <gui/CpuConsumer.h>
-
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
+#include <gui/CpuConsumer.h>
 #include <utils/Log.h>
 
 #define CC_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
@@ -31,12 +31,25 @@
 
 namespace android {
 
-CpuConsumer::CpuConsumer(const sp<IGraphicBufferConsumer>& bq,
-        size_t maxLockedBuffers, bool controlledByApp) :
-    ConsumerBase(bq, controlledByApp),
-    mMaxLockedBuffers(maxLockedBuffers),
-    mCurrentLockedBuffers(0)
-{
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+CpuConsumer::CpuConsumer(size_t maxLockedBuffers, bool controlledByApp,
+                         bool isConsumerSurfaceFlinger)
+      : ConsumerBase(controlledByApp, isConsumerSurfaceFlinger),
+        mMaxLockedBuffers(maxLockedBuffers),
+        mCurrentLockedBuffers(0) {
+    // Create tracking entries for locked buffers
+    mAcquiredBuffers.insertAt(0, maxLockedBuffers);
+
+    mConsumer->setConsumerUsageBits(GRALLOC_USAGE_SW_READ_OFTEN);
+    mConsumer->setMaxAcquiredBufferCount(static_cast<int32_t>(maxLockedBuffers));
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
+CpuConsumer::CpuConsumer(const sp<IGraphicBufferConsumer>& bq, size_t maxLockedBuffers,
+                         bool controlledByApp)
+      : ConsumerBase(bq, controlledByApp),
+        mMaxLockedBuffers(maxLockedBuffers),
+        mCurrentLockedBuffers(0) {
     // Create tracking entries for locked buffers
     mAcquiredBuffers.insertAt(0, maxLockedBuffers);
 
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index f3de96d..c46f9c5 100644
--- a/libs/gui/DisplayEventDispatcher.cpp
+++ b/libs/gui/DisplayEventDispatcher.cpp
@@ -15,6 +15,7 @@
  */
 
 #define LOG_TAG "DisplayEventDispatcher"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <cinttypes>
 #include <cstdint>
@@ -23,10 +24,13 @@
 #include <gui/DisplayEventReceiver.h>
 #include <utils/Log.h>
 #include <utils/Looper.h>
-
 #include <utils/Timers.h>
+#include <utils/Trace.h>
+
+#include <com_android_graphics_libgui_flags.h>
 
 namespace android {
+using namespace com::android::graphics::libgui;
 
 // Number of events to read at a time from the DisplayEventDispatcher pipe.
 // The value should be large enough that we can quickly drain the pipe
@@ -171,6 +175,13 @@
                     *outDisplayId = ev.header.displayId;
                     *outCount = ev.vsync.count;
                     *outVsyncEventData = ev.vsync.vsyncData;
+
+                    // Trace the RenderRate for this app
+                    if (ATRACE_ENABLED() && flags::trace_frame_rate_override()) {
+                        const auto frameInterval = ev.vsync.vsyncData.frameInterval;
+                        int fps = frameInterval > 0 ? 1e9f / frameInterval : 0;
+                        ATRACE_INT("RenderRate", fps);
+                    }
                     break;
                 case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
                     if (ev.hotplug.connectionError == 0) {
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
index bd640df..47cec07 100644
--- a/libs/gui/DisplayInfo.cpp
+++ b/libs/gui/DisplayInfo.cpp
@@ -37,8 +37,9 @@
         return BAD_VALUE;
     }
 
+    int32_t displayIdInt;
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    SAFE_PARCEL(parcel->readInt32, &displayId);
+    SAFE_PARCEL(parcel->readInt32, &displayIdInt);
     SAFE_PARCEL(parcel->readInt32, &logicalWidth);
     SAFE_PARCEL(parcel->readInt32, &logicalHeight);
     SAFE_PARCEL(parcel->readFloat, &dsdx);
@@ -48,6 +49,7 @@
     SAFE_PARCEL(parcel->readFloat, &dsdy);
     SAFE_PARCEL(parcel->readFloat, &ty);
 
+    displayId = ui::LogicalDisplayId{displayIdInt};
     transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
 
     return OK;
@@ -59,7 +61,7 @@
         return BAD_VALUE;
     }
 
-    SAFE_PARCEL(parcel->writeInt32, displayId);
+    SAFE_PARCEL(parcel->writeInt32, displayId.val());
     SAFE_PARCEL(parcel->writeInt32, logicalWidth);
     SAFE_PARCEL(parcel->writeInt32, logicalHeight);
     SAFE_PARCEL(parcel->writeFloat, transform.dsdx());
@@ -76,7 +78,7 @@
     using android::base::StringAppendF;
 
     out += prefix;
-    StringAppendF(&out, "DisplayViewport[id=%" PRId32 "]\n", displayId);
+    StringAppendF(&out, "DisplayViewport[id=%s]\n", displayId.toString().c_str());
     out += prefix;
     StringAppendF(&out, INDENT "Width=%" PRId32 ", Height=%" PRId32 "\n", logicalWidth,
                   logicalHeight);
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index d49489c..95cce5c 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -101,6 +101,34 @@
     return hasIt;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+GLConsumer::GLConsumer(uint32_t tex, uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
+      : ConsumerBase(isControlledByApp, /* isConsumerSurfaceFlinger */ false),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(tex),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mAttached(true) {
+    GLC_LOGV("GLConsumer");
+
+    memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
+
+    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
         uint32_t texTarget, bool useFenceSync, bool isControlledByApp) :
     ConsumerBase(bq, isControlledByApp),
@@ -130,27 +158,54 @@
     mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
 }
 
-GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget,
-        bool useFenceSync, bool isControlledByApp) :
-    ConsumerBase(bq, isControlledByApp),
-    mCurrentCrop(Rect::EMPTY_RECT),
-    mCurrentTransform(0),
-    mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
-    mCurrentFence(Fence::NO_FENCE),
-    mCurrentTimestamp(0),
-    mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
-    mCurrentFrameNumber(0),
-    mDefaultWidth(1),
-    mDefaultHeight(1),
-    mFilteringEnabled(true),
-    mTexName(0),
-    mUseFenceSync(useFenceSync),
-    mTexTarget(texTarget),
-    mEglDisplay(EGL_NO_DISPLAY),
-    mEglContext(EGL_NO_CONTEXT),
-    mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
-    mAttached(false)
-{
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+GLConsumer::GLConsumer(uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
+      : ConsumerBase(isControlledByApp, /* isConsumerSurfaceFlinger */ false),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(0),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mAttached(false) {
+    GLC_LOGV("GLConsumer");
+
+    memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
+
+    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
+GLConsumer::GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget, bool useFenceSync,
+                       bool isControlledByApp)
+      : ConsumerBase(bq, isControlledByApp),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(0),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mEglDisplay(EGL_NO_DISPLAY),
+        mEglContext(EGL_NO_CONTEXT),
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mAttached(false) {
     GLC_LOGV("GLConsumer");
 
     memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(),
diff --git a/libs/gui/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index e81c098..0914480 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -80,6 +80,7 @@
     QUERY_MULTIPLE,
     GET_LAST_QUEUED_BUFFER2,
     SET_FRAME_RATE,
+    SET_ADDITIONAL_OPTIONS,
 };
 
 class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -778,6 +779,25 @@
         return result;
     }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+        if (options.size() > 100) {
+            return BAD_VALUE;
+        }
+        data.writeInt32(options.size());
+        for (const auto& it : options) {
+            data.writeCString(it.name.c_str());
+            data.writeInt64(it.value);
+        }
+        status_t result = remote()->transact(SET_ADDITIONAL_OPTIONS, data, &reply);
+        if (result == NO_ERROR) {
+            result = reply.readInt32();
+        }
+        return result;
+    }
+#endif
 };
 
 // Out-of-line virtual method definition to trigger vtable emission in this
@@ -981,6 +1001,13 @@
 }
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t IGraphicBufferProducer::setAdditionalOptions(const std::vector<gui::AdditionalOptions>&) {
+    // No-op for IGBP other than BufferQueue.
+    return INVALID_OPERATION;
+}
+#endif
+
 status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
     status_t res = OK;
     res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1533,6 +1560,28 @@
             return NO_ERROR;
         }
 #endif
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+        case SET_ADDITIONAL_OPTIONS: {
+            CHECK_INTERFACE(IGraphicBuffer, data, reply);
+            int optionCount = data.readInt32();
+            if (optionCount < 0 || optionCount > 100) {
+                return BAD_VALUE;
+            }
+            std::vector<gui::AdditionalOptions> opts;
+            opts.reserve(optionCount);
+            for (int i = 0; i < optionCount; i++) {
+                const char* name = data.readCString();
+                int64_t value = 0;
+                if (name == nullptr || data.readInt64(&value) != NO_ERROR) {
+                    return BAD_VALUE;
+                }
+                opts.emplace_back(name, value);
+            }
+            status_t result = setAdditionalOptions(opts);
+            reply->writeInt32(result);
+            return NO_ERROR;
+        }
+#endif
     }
     return BBinder::onTransact(code, data, reply, flags);
 }
diff --git a/libs/gui/IProducerListener.cpp b/libs/gui/IProducerListener.cpp
index 7700795..8b9b090 100644
--- a/libs/gui/IProducerListener.cpp
+++ b/libs/gui/IProducerListener.cpp
@@ -184,4 +184,10 @@
 void BnProducerListener::onBuffersDiscarded(const std::vector<int32_t>& /*discardedSlots*/) {
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+bool BnProducerListener::needsAttachNotify() {
+    return true;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index ff6b558..2699368 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -62,7 +62,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
             const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
             const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index f5d19aa..83fc827 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -43,12 +43,6 @@
 
 } // Anonymous namespace
 
-namespace { // Anonymous
-
-constexpr int32_t kSerializedCallbackTypeOnCompelteWithJankData = 2;
-
-} // Anonymous namespace
-
 status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
     status_t err = output->writeUint64(frameNumber);
     if (err != NO_ERROR) return err;
@@ -119,23 +113,6 @@
     return err;
 }
 
-JankData::JankData()
-      : frameVsyncId(FrameTimelineInfo::INVALID_VSYNC_ID), jankType(JankType::None) {}
-
-status_t JankData::writeToParcel(Parcel* output) const {
-    SAFE_PARCEL(output->writeInt64, frameVsyncId);
-    SAFE_PARCEL(output->writeInt32, jankType);
-    SAFE_PARCEL(output->writeInt64, frameIntervalNs);
-    return NO_ERROR;
-}
-
-status_t JankData::readFromParcel(const Parcel* input) {
-    SAFE_PARCEL(input->readInt64, &frameVsyncId);
-    SAFE_PARCEL(input->readInt32, &jankType);
-    SAFE_PARCEL(input->readInt64, &frameIntervalNs);
-    return NO_ERROR;
-}
-
 status_t SurfaceStats::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeStrongBinder, surfaceControl);
     if (const auto* acquireFence = std::get_if<sp<Fence>>(&acquireTimeOrFence)) {
@@ -160,10 +137,6 @@
 
     SAFE_PARCEL(output->writeUint32, currentMaxAcquiredBufferCount);
     SAFE_PARCEL(output->writeParcelable, eventStats);
-    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(jankData.size()));
-    for (const auto& data : jankData) {
-        SAFE_PARCEL(output->writeParcelable, data);
-    }
     SAFE_PARCEL(output->writeParcelable, previousReleaseCallbackId);
     return NO_ERROR;
 }
@@ -200,13 +173,6 @@
     SAFE_PARCEL(input->readUint32, &currentMaxAcquiredBufferCount);
     SAFE_PARCEL(input->readParcelable, &eventStats);
 
-    int32_t jankData_size = 0;
-    SAFE_PARCEL_READ_SIZE(input->readInt32, &jankData_size, input->dataSize());
-    for (int i = 0; i < jankData_size; i++) {
-        JankData data;
-        SAFE_PARCEL(input->readParcelable, &data);
-        jankData.push_back(data);
-    }
     SAFE_PARCEL(input->readParcelable, &previousReleaseCallbackId);
     return NO_ERROR;
 }
@@ -371,11 +337,7 @@
 
 status_t CallbackId::writeToParcel(Parcel* output) const {
     SAFE_PARCEL(output->writeInt64, id);
-    if (type == Type::ON_COMPLETE && includeJankData) {
-        SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData);
-    } else {
-        SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
-    }
+    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
     return NO_ERROR;
 }
 
@@ -383,13 +345,7 @@
     SAFE_PARCEL(input->readInt64, &id);
     int32_t typeAsInt;
     SAFE_PARCEL(input->readInt32, &typeAsInt);
-    if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) {
-        type = Type::ON_COMPLETE;
-        includeJankData = true;
-    } else {
-        type = static_cast<CallbackId::Type>(typeAsInt);
-        includeJankData = false;
-    }
+    type = static_cast<CallbackId::Type>(typeAsInt);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
deleted file mode 100644
index 15b2221..0000000
--- a/libs/gui/LayerDebugInfo.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gui/LayerDebugInfo.h>
-
-#include <android-base/stringprintf.h>
-
-#include <ui/DebugUtils.h>
-
-#include <binder/Parcel.h>
-
-using namespace android;
-using android::base::StringAppendF;
-
-#define RETURN_ON_ERROR(X) do {status_t res = (X); if (res != NO_ERROR) return res;} while(false)
-
-namespace android::gui {
-
-status_t LayerDebugInfo::writeToParcel(Parcel* parcel) const {
-    RETURN_ON_ERROR(parcel->writeCString(mName.c_str()));
-    RETURN_ON_ERROR(parcel->writeCString(mParentName.c_str()));
-    RETURN_ON_ERROR(parcel->writeCString(mType.c_str()));
-    RETURN_ON_ERROR(parcel->write(mTransparentRegion));
-    RETURN_ON_ERROR(parcel->write(mVisibleRegion));
-    RETURN_ON_ERROR(parcel->write(mSurfaceDamageRegion));
-    RETURN_ON_ERROR(parcel->writeUint32(mLayerStack));
-    RETURN_ON_ERROR(parcel->writeFloat(mX));
-    RETURN_ON_ERROR(parcel->writeFloat(mY));
-    RETURN_ON_ERROR(parcel->writeUint32(mZ));
-    RETURN_ON_ERROR(parcel->writeInt32(mWidth));
-    RETURN_ON_ERROR(parcel->writeInt32(mHeight));
-    RETURN_ON_ERROR(parcel->write(mCrop));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.r));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.g));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.b));
-    RETURN_ON_ERROR(parcel->writeFloat(mColor.a));
-    RETURN_ON_ERROR(parcel->writeUint32(mFlags));
-    RETURN_ON_ERROR(parcel->writeInt32(mPixelFormat));
-    RETURN_ON_ERROR(parcel->writeUint32(static_cast<uint32_t>(mDataSpace)));
-    for (size_t index = 0; index < 4; index++) {
-        RETURN_ON_ERROR(parcel->writeFloat(mMatrix[index / 2][index % 2]));
-    }
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferWidth));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferHeight));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferStride));
-    RETURN_ON_ERROR(parcel->writeInt32(mActiveBufferFormat));
-    RETURN_ON_ERROR(parcel->writeInt32(mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->writeBool(mIsOpaque));
-    RETURN_ON_ERROR(parcel->writeBool(mContentDirty));
-    RETURN_ON_ERROR(parcel->write(mStretchEffect));
-    return NO_ERROR;
-}
-
-status_t LayerDebugInfo::readFromParcel(const Parcel* parcel) {
-    mName = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mParentName = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mType = parcel->readCString();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    RETURN_ON_ERROR(parcel->read(mTransparentRegion));
-    RETURN_ON_ERROR(parcel->read(mVisibleRegion));
-    RETURN_ON_ERROR(parcel->read(mSurfaceDamageRegion));
-    RETURN_ON_ERROR(parcel->readUint32(&mLayerStack));
-    RETURN_ON_ERROR(parcel->readFloat(&mX));
-    RETURN_ON_ERROR(parcel->readFloat(&mY));
-    RETURN_ON_ERROR(parcel->readUint32(&mZ));
-    RETURN_ON_ERROR(parcel->readInt32(&mWidth));
-    RETURN_ON_ERROR(parcel->readInt32(&mHeight));
-    RETURN_ON_ERROR(parcel->read(mCrop));
-    mColor.r = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.g = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.b = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    mColor.a = parcel->readFloat();
-    RETURN_ON_ERROR(parcel->errorCheck());
-    RETURN_ON_ERROR(parcel->readUint32(&mFlags));
-    RETURN_ON_ERROR(parcel->readInt32(&mPixelFormat));
-    // \todo [2017-07-25 kraita]: Static casting mDataSpace pointer to an uint32 does work. Better ways?
-    mDataSpace = static_cast<android_dataspace>(parcel->readUint32());
-    RETURN_ON_ERROR(parcel->errorCheck());
-    for (size_t index = 0; index < 4; index++) {
-        RETURN_ON_ERROR(parcel->readFloat(&mMatrix[index / 2][index % 2]));
-    }
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferWidth));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferHeight));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferStride));
-    RETURN_ON_ERROR(parcel->readInt32(&mActiveBufferFormat));
-    RETURN_ON_ERROR(parcel->readInt32(&mNumQueuedFrames));
-    RETURN_ON_ERROR(parcel->readBool(&mIsOpaque));
-    RETURN_ON_ERROR(parcel->readBool(&mContentDirty));
-    RETURN_ON_ERROR(parcel->read(mStretchEffect));
-    return NO_ERROR;
-}
-
-std::string to_string(const LayerDebugInfo& info) {
-    std::string result;
-
-    StringAppendF(&result, "+ %s (%s)\n", info.mType.c_str(), info.mName.c_str());
-    info.mTransparentRegion.dump(result, "TransparentRegion");
-    info.mVisibleRegion.dump(result, "VisibleRegion");
-    info.mSurfaceDamageRegion.dump(result, "SurfaceDamageRegion");
-    if (info.mStretchEffect.hasEffect()) {
-        const auto& se = info.mStretchEffect;
-        StringAppendF(&result,
-                      "  StretchEffect width = %f, height = %f vec=(%f, %f) "
-                      "maxAmount=(%f, %f)\n",
-                      se.width, se.height,
-                      se.vectorX, se.vectorY, se.maxAmountX, se.maxAmountY);
-    }
-
-    StringAppendF(&result, "      layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ",
-                  info.mLayerStack, info.mZ, static_cast<double>(info.mX),
-                  static_cast<double>(info.mY), info.mWidth, info.mHeight);
-
-    StringAppendF(&result, "crop=%s, ", to_string(info.mCrop).c_str());
-    StringAppendF(&result, "isOpaque=%1d, invalidate=%1d, ", info.mIsOpaque, info.mContentDirty);
-    StringAppendF(&result, "dataspace=%s, ", dataspaceDetails(info.mDataSpace).c_str());
-    StringAppendF(&result, "pixelformat=%s, ", decodePixelFormat(info.mPixelFormat).c_str());
-    StringAppendF(&result, "color=(%.3f,%.3f,%.3f,%.3f), flags=0x%08x, ",
-                  static_cast<double>(info.mColor.r), static_cast<double>(info.mColor.g),
-                  static_cast<double>(info.mColor.b), static_cast<double>(info.mColor.a),
-                  info.mFlags);
-    StringAppendF(&result, "tr=[%.2f, %.2f][%.2f, %.2f]", static_cast<double>(info.mMatrix[0][0]),
-                  static_cast<double>(info.mMatrix[0][1]), static_cast<double>(info.mMatrix[1][0]),
-                  static_cast<double>(info.mMatrix[1][1]));
-    result.append("\n");
-    StringAppendF(&result, "      parent=%s\n", info.mParentName.c_str());
-    StringAppendF(&result, "      activeBuffer=[%4ux%4u:%4u,%s],", info.mActiveBufferWidth,
-                  info.mActiveBufferHeight, info.mActiveBufferStride,
-                  decodePixelFormat(info.mActiveBufferFormat).c_str());
-    StringAppendF(&result, " queued-frames=%d", info.mNumQueuedFrames);
-    result.append("\n");
-    return result;
-}
-
-} // namespace android::gui
diff --git a/libs/gui/LayerMetadata.cpp b/libs/gui/LayerMetadata.cpp
index 4e12fd3..535a021 100644
--- a/libs/gui/LayerMetadata.cpp
+++ b/libs/gui/LayerMetadata.cpp
@@ -100,27 +100,31 @@
 int32_t LayerMetadata::getInt32(uint32_t key, int32_t fallback) const {
     if (!has(key)) return fallback;
     const std::vector<uint8_t>& data = mMap.at(key);
-    if (data.size() < sizeof(uint32_t)) return fallback;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt32();
+
+    // TODO: should handle when not equal?
+    if (data.size() < sizeof(int32_t)) return fallback;
+
+    int32_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 void LayerMetadata::setInt32(uint32_t key, int32_t value) {
     std::vector<uint8_t>& data = mMap[key];
-    Parcel p;
-    p.writeInt32(value);
-    data.resize(p.dataSize());
-    memcpy(data.data(), p.data(), p.dataSize());
+    data.resize(sizeof(value));
+    memcpy(data.data(), &value, sizeof(value));
 }
 
 std::optional<int64_t> LayerMetadata::getInt64(uint32_t key) const {
     if (!has(key)) return std::nullopt;
     const std::vector<uint8_t>& data = mMap.at(key);
+
+    // TODO: should handle when not equal?
     if (data.size() < sizeof(int64_t)) return std::nullopt;
-    Parcel p;
-    p.setData(data.data(), data.size());
-    return p.readInt64();
+
+    int64_t result;
+    memcpy(&result, data.data(), sizeof(result));
+    return result;
 }
 
 std::string LayerMetadata::itemToString(uint32_t key, const char* separator) const {
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 1e0aacd..b109969 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -17,7 +17,6 @@
 #define LOG_TAG "LayerState"
 
 #include <cinttypes>
-#include <cmath>
 
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/native_window.h>
@@ -89,8 +88,7 @@
         frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE),
         fixedTransformHint(ui::Transform::ROT_INVALID),
         autoRefresh(false),
-        isTrustedOverlay(false),
-        borderEnabled(false),
+        trustedOverlay(gui::TrustedOverlay::UNSET),
         bufferCrop(Rect::INVALID_RECT),
         destinationFrame(Rect::INVALID_RECT),
         dropInputMode(gui::DropInputMode::NONE) {
@@ -122,12 +120,6 @@
     SAFE_PARCEL(output.write, transparentRegion);
     SAFE_PARCEL(output.writeUint32, bufferTransform);
     SAFE_PARCEL(output.writeBool, transformToDisplayInverse);
-    SAFE_PARCEL(output.writeBool, borderEnabled);
-    SAFE_PARCEL(output.writeFloat, borderWidth);
-    SAFE_PARCEL(output.writeFloat, borderColor.r);
-    SAFE_PARCEL(output.writeFloat, borderColor.g);
-    SAFE_PARCEL(output.writeFloat, borderColor.b);
-    SAFE_PARCEL(output.writeFloat, borderColor.a);
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dataspace));
     SAFE_PARCEL(output.write, hdrMetadata);
     SAFE_PARCEL(output.write, surfaceDamageRegion);
@@ -184,9 +176,10 @@
     }
 
     SAFE_PARCEL(output.write, stretchEffect);
+    SAFE_PARCEL(output.writeParcelable, edgeExtensionParameters);
     SAFE_PARCEL(output.write, bufferCrop);
     SAFE_PARCEL(output.write, destinationFrame);
-    SAFE_PARCEL(output.writeBool, isTrustedOverlay);
+    SAFE_PARCEL(output.writeInt32, static_cast<uint32_t>(trustedOverlay));
 
     SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dropInputMode));
 
@@ -200,6 +193,13 @@
     SAFE_PARCEL(output.writeFloat, currentHdrSdrRatio);
     SAFE_PARCEL(output.writeFloat, desiredHdrSdrRatio);
     SAFE_PARCEL(output.writeInt32, static_cast<int32_t>(cachingHint));
+
+    const bool hasBufferReleaseChannel = (bufferReleaseChannel != nullptr);
+    SAFE_PARCEL(output.writeBool, hasBufferReleaseChannel);
+    if (hasBufferReleaseChannel) {
+        SAFE_PARCEL(output.writeParcelable, *bufferReleaseChannel);
+    }
+
     return NO_ERROR;
 }
 
@@ -238,17 +238,6 @@
     SAFE_PARCEL(input.read, transparentRegion);
     SAFE_PARCEL(input.readUint32, &bufferTransform);
     SAFE_PARCEL(input.readBool, &transformToDisplayInverse);
-    SAFE_PARCEL(input.readBool, &borderEnabled);
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderWidth = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.r = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.g = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.b = tmpFloat;
-    SAFE_PARCEL(input.readFloat, &tmpFloat);
-    borderColor.a = tmpFloat;
 
     uint32_t tmpUint32 = 0;
     SAFE_PARCEL(input.readUint32, &tmpUint32);
@@ -324,9 +313,12 @@
     }
 
     SAFE_PARCEL(input.read, stretchEffect);
+    SAFE_PARCEL(input.readParcelable, &edgeExtensionParameters);
     SAFE_PARCEL(input.read, bufferCrop);
     SAFE_PARCEL(input.read, destinationFrame);
-    SAFE_PARCEL(input.readBool, &isTrustedOverlay);
+    uint32_t trustedOverlayInt;
+    SAFE_PARCEL(input.readUint32, &trustedOverlayInt);
+    trustedOverlay = static_cast<gui::TrustedOverlay>(trustedOverlayInt);
 
     uint32_t mode;
     SAFE_PARCEL(input.readUint32, &mode);
@@ -353,6 +345,13 @@
     SAFE_PARCEL(input.readInt32, &tmpInt32);
     cachingHint = static_cast<gui::CachingHint>(tmpInt32);
 
+    bool hasBufferReleaseChannel;
+    SAFE_PARCEL(input.readBool, &hasBufferReleaseChannel);
+    if (hasBufferReleaseChannel) {
+        bufferReleaseChannel = std::make_shared<gui::BufferReleaseChannel::ProducerEndpoint>();
+        SAFE_PARCEL(input.readParcelable, bufferReleaseChannel.get());
+    }
+
     return NO_ERROR;
 }
 
@@ -659,12 +658,6 @@
         what |= eShadowRadiusChanged;
         shadowRadius = other.shadowRadius;
     }
-    if (other.what & eRenderBorderChanged) {
-        what |= eRenderBorderChanged;
-        borderEnabled = other.borderEnabled;
-        borderWidth = other.borderWidth;
-        borderColor = other.borderColor;
-    }
     if (other.what & eDefaultFrameRateCompatibilityChanged) {
         what |= eDefaultFrameRateCompatibilityChanged;
         defaultFrameRateCompatibility = other.defaultFrameRateCompatibility;
@@ -698,12 +691,16 @@
     }
     if (other.what & eTrustedOverlayChanged) {
         what |= eTrustedOverlayChanged;
-        isTrustedOverlay = other.isTrustedOverlay;
+        trustedOverlay = other.trustedOverlay;
     }
     if (other.what & eStretchChanged) {
         what |= eStretchChanged;
         stretchEffect = other.stretchEffect;
     }
+    if (other.what & eEdgeExtensionChanged) {
+        what |= eEdgeExtensionChanged;
+        edgeExtensionParameters = other.edgeExtensionParameters;
+    }
     if (other.what & eBufferCropChanged) {
         what |= eBufferCropChanged;
         bufferCrop = other.bufferCrop;
@@ -734,6 +731,10 @@
     if (other.what & eFlushJankData) {
         what |= eFlushJankData;
     }
+    if (other.what & eBufferReleaseChannelChanged) {
+        what |= eBufferReleaseChannelChanged;
+        bufferReleaseChannel = other.bufferReleaseChannel;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
@@ -794,7 +795,6 @@
     CHECK_DIFF2(diff, eBackgroundColorChanged, other, bgColor, bgColorDataspace);
     if (other.what & eMetadataChanged) diff |= eMetadataChanged;
     CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
-    CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
     CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
     CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
     CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
@@ -804,8 +804,9 @@
     CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
     CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
     CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
-    CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
+    CHECK_DIFF(diff, eTrustedOverlayChanged, other, trustedOverlay);
     CHECK_DIFF(diff, eStretchChanged, other, stretchEffect);
+    CHECK_DIFF(diff, eEdgeExtensionChanged, other, edgeExtensionParameters);
     CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop);
     CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame);
     if (other.what & eProducerDisconnect) diff |= eProducerDisconnect;
@@ -813,6 +814,7 @@
     CHECK_DIFF(diff, eColorChanged, other, color.rgb);
     CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
     CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
+    if (other.what & eBufferReleaseChannelChanged) diff |= eBufferReleaseChannelChanged;
     return diff;
 }
 
@@ -890,88 +892,6 @@
 
 // ----------------------------------------------------------------------------
 
-namespace gui {
-
-status_t CaptureArgs::writeToParcel(Parcel* output) const {
-    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(pixelFormat));
-    SAFE_PARCEL(output->write, sourceCrop);
-    SAFE_PARCEL(output->writeFloat, frameScaleX);
-    SAFE_PARCEL(output->writeFloat, frameScaleY);
-    SAFE_PARCEL(output->writeBool, captureSecureLayers);
-    SAFE_PARCEL(output->writeInt32, uid);
-    SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(dataspace));
-    SAFE_PARCEL(output->writeBool, allowProtected);
-    SAFE_PARCEL(output->writeBool, grayscale);
-    SAFE_PARCEL(output->writeInt32, excludeHandles.size());
-    for (auto& excludeHandle : excludeHandles) {
-        SAFE_PARCEL(output->writeStrongBinder, excludeHandle);
-    }
-    SAFE_PARCEL(output->writeBool, hintForSeamlessTransition);
-    return NO_ERROR;
-}
-
-status_t CaptureArgs::readFromParcel(const Parcel* input) {
-    int32_t value = 0;
-    SAFE_PARCEL(input->readInt32, &value);
-    pixelFormat = static_cast<ui::PixelFormat>(value);
-    SAFE_PARCEL(input->read, sourceCrop);
-    SAFE_PARCEL(input->readFloat, &frameScaleX);
-    SAFE_PARCEL(input->readFloat, &frameScaleY);
-    SAFE_PARCEL(input->readBool, &captureSecureLayers);
-    SAFE_PARCEL(input->readInt32, &uid);
-    SAFE_PARCEL(input->readInt32, &value);
-    dataspace = static_cast<ui::Dataspace>(value);
-    SAFE_PARCEL(input->readBool, &allowProtected);
-    SAFE_PARCEL(input->readBool, &grayscale);
-    int32_t numExcludeHandles = 0;
-    SAFE_PARCEL_READ_SIZE(input->readInt32, &numExcludeHandles, input->dataSize());
-    excludeHandles.reserve(numExcludeHandles);
-    for (int i = 0; i < numExcludeHandles; i++) {
-        sp<IBinder> binder;
-        SAFE_PARCEL(input->readStrongBinder, &binder);
-        excludeHandles.emplace(binder);
-    }
-    SAFE_PARCEL(input->readBool, &hintForSeamlessTransition);
-    return NO_ERROR;
-}
-
-status_t DisplayCaptureArgs::writeToParcel(Parcel* output) const {
-    SAFE_PARCEL(CaptureArgs::writeToParcel, output);
-
-    SAFE_PARCEL(output->writeStrongBinder, displayToken);
-    SAFE_PARCEL(output->writeUint32, width);
-    SAFE_PARCEL(output->writeUint32, height);
-    return NO_ERROR;
-}
-
-status_t DisplayCaptureArgs::readFromParcel(const Parcel* input) {
-    SAFE_PARCEL(CaptureArgs::readFromParcel, input);
-
-    SAFE_PARCEL(input->readStrongBinder, &displayToken);
-    SAFE_PARCEL(input->readUint32, &width);
-    SAFE_PARCEL(input->readUint32, &height);
-    return NO_ERROR;
-}
-
-status_t LayerCaptureArgs::writeToParcel(Parcel* output) const {
-    SAFE_PARCEL(CaptureArgs::writeToParcel, output);
-
-    SAFE_PARCEL(output->writeStrongBinder, layerHandle);
-    SAFE_PARCEL(output->writeBool, childrenOnly);
-    return NO_ERROR;
-}
-
-status_t LayerCaptureArgs::readFromParcel(const Parcel* input) {
-    SAFE_PARCEL(CaptureArgs::readFromParcel, input);
-
-    SAFE_PARCEL(input->readStrongBinder, &layerHandle);
-
-    SAFE_PARCEL(input->readBool, &childrenOnly);
-    return NO_ERROR;
-}
-
-}; // namespace gui
-
 ReleaseCallbackId BufferData::generateReleaseCallbackId() const {
     uint64_t bufferId;
     if (buffer) {
@@ -1008,6 +928,7 @@
     SAFE_PARCEL(output->writeBool, hasBarrier);
     SAFE_PARCEL(output->writeUint64, barrierFrameNumber);
     SAFE_PARCEL(output->writeUint32, producerId);
+    SAFE_PARCEL(output->writeInt64, dequeueTime);
 
     return NO_ERROR;
 }
@@ -1047,6 +968,7 @@
     SAFE_PARCEL(input->readBool, &hasBarrier);
     SAFE_PARCEL(input->readUint64, &barrierFrameNumber);
     SAFE_PARCEL(input->readUint32, &producerId);
+    SAFE_PARCEL(input->readInt64, &dequeueTime);
 
     return NO_ERROR;
 }
diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
index 28697ca..c467cfd 100644
--- a/libs/gui/LayerStatePermissions.cpp
+++ b/libs/gui/LayerStatePermissions.cpp
@@ -23,31 +23,31 @@
 #include <gui/LayerState.h>
 
 namespace android {
-std::unordered_map<std::string, int> LayerStatePermissions::mPermissionMap = {
+std::vector<std::pair<String16, int>> LayerStatePermissions::mPermissionMap = {
         // If caller has ACCESS_SURFACE_FLINGER, they automatically get ROTATE_SURFACE_FLINGER
         // permission, as well
-        {"android.permission.ACCESS_SURFACE_FLINGER",
+        {String16("android.permission.ACCESS_SURFACE_FLINGER"),
          layer_state_t::Permission::ACCESS_SURFACE_FLINGER |
                  layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.ROTATE_SURFACE_FLINGER",
+        {String16("android.permission.ROTATE_SURFACE_FLINGER"),
          layer_state_t::Permission::ROTATE_SURFACE_FLINGER},
-        {"android.permission.INTERNAL_SYSTEM_WINDOW",
+        {String16("android.permission.INTERNAL_SYSTEM_WINDOW"),
          layer_state_t::Permission::INTERNAL_SYSTEM_WINDOW},
 };
 
-static bool callingThreadHasPermission(const std::string& permission __attribute__((unused)),
+static bool callingThreadHasPermission(const String16& permission __attribute__((unused)),
                                        int pid __attribute__((unused)),
                                        int uid __attribute__((unused))) {
 #ifndef __ANDROID_VNDK__
     return uid == AID_GRAPHICS || uid == AID_SYSTEM ||
-            PermissionCache::checkPermission(String16(permission.c_str()), pid, uid);
+            PermissionCache::checkPermission(permission, pid, uid);
 #endif // __ANDROID_VNDK__
     return false;
 }
 
 uint32_t LayerStatePermissions::getTransactionPermissions(int pid, int uid) {
     uint32_t permissions = 0;
-    for (auto [permissionName, permissionVal] : mPermissionMap) {
+    for (const auto& [permissionName, permissionVal] : mPermissionMap) {
         if (callingThreadHasPermission(permissionName, pid, uid)) {
             permissions |= permissionVal;
         }
diff --git a/libs/gui/ScreenCaptureResults.cpp b/libs/gui/ScreenCaptureResults.cpp
index 601a5f9..2de023e 100644
--- a/libs/gui/ScreenCaptureResults.cpp
+++ b/libs/gui/ScreenCaptureResults.cpp
@@ -40,6 +40,13 @@
     SAFE_PARCEL(parcel->writeBool, capturedSecureLayers);
     SAFE_PARCEL(parcel->writeBool, capturedHdrLayers);
     SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(capturedDataspace));
+    if (optionalGainMap != nullptr) {
+        SAFE_PARCEL(parcel->writeBool, true);
+        SAFE_PARCEL(parcel->write, *optionalGainMap);
+    } else {
+        SAFE_PARCEL(parcel->writeBool, false);
+    }
+    SAFE_PARCEL(parcel->writeFloat, hdrSdrRatio);
     return NO_ERROR;
 }
 
@@ -68,6 +75,14 @@
     uint32_t dataspace = 0;
     SAFE_PARCEL(parcel->readUint32, &dataspace);
     capturedDataspace = static_cast<ui::Dataspace>(dataspace);
+
+    bool hasGainmap;
+    SAFE_PARCEL(parcel->readBool, &hasGainmap);
+    if (hasGainmap) {
+        optionalGainMap = new GraphicBuffer();
+        SAFE_PARCEL(parcel->read, *optionalGainMap);
+    }
+    SAFE_PARCEL(parcel->readFloat, &hdrSdrRatio);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 086544e..66e7ddd 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -21,6 +21,8 @@
 #include <gui/Surface.h>
 
 #include <condition_variable>
+#include <cstddef>
+#include <cstdint>
 #include <deque>
 #include <mutex>
 #include <thread>
@@ -41,11 +43,9 @@
 #include <ui/GraphicBuffer.h>
 #include <ui/Region.h>
 
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/BufferItem.h>
 
-#include <gui/IProducerListener.h>
-
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerState.h>
 #include <private/gui/ComposerService.h>
@@ -77,9 +77,28 @@
 
 } // namespace
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+Surface::ProducerDeathListenerProxy::ProducerDeathListenerProxy(wp<SurfaceListener> surfaceListener)
+      : mSurfaceListener(surfaceListener) {}
+
+void Surface::ProducerDeathListenerProxy::binderDied(const wp<IBinder>&) {
+    sp<SurfaceListener> surfaceListener = mSurfaceListener.promote();
+    if (!surfaceListener) {
+        return;
+    }
+
+    if (surfaceListener->needsDeathNotify()) {
+        surfaceListener->onRemoteDied();
+    }
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 Surface::Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controlledByApp,
                  const sp<IBinder>& surfaceControlHandle)
       : mGraphicBufferProducer(bufferProducer),
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+        mSurfaceDeathListener(nullptr),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
         mCrop(Rect::EMPTY_RECT),
         mBufferAge(0),
         mGenerationNumber(0),
@@ -134,6 +153,12 @@
     if (mConnectedToCpu) {
         Surface::disconnect(NATIVE_WINDOW_API_CPU);
     }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    if (mSurfaceDeathListener != nullptr) {
+        IInterface::asBinder(mGraphicBufferProducer)->unlinkToDeath(mSurfaceDeathListener);
+        mSurfaceDeathListener = nullptr;
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 }
 
 sp<ISurfaceComposer> Surface::composerService() const {
@@ -163,6 +188,12 @@
             mReqFormat, mReqUsage);
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+status_t Surface::allowAllocation(bool allowAllocation) {
+    return mGraphicBufferProducer->allowAllocation(allowAllocation);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 status_t Surface::setGenerationNumber(uint32_t generation) {
     status_t result = mGraphicBufferProducer->setGenerationNumber(generation);
     if (result == NO_ERROR) {
@@ -695,6 +726,51 @@
     return OK;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+status_t Surface::dequeueBuffer(sp<GraphicBuffer>* buffer, sp<Fence>* outFence) {
+    if (buffer == nullptr || outFence == nullptr) {
+        return BAD_VALUE;
+    }
+
+    android_native_buffer_t* anb;
+    int fd = -1;
+    status_t res = dequeueBuffer(&anb, &fd);
+    *buffer = GraphicBuffer::from(anb);
+    *outFence = sp<Fence>::make(fd);
+    return res;
+}
+
+status_t Surface::queueBuffer(const sp<GraphicBuffer>& buffer, const sp<Fence>& fd,
+                              SurfaceQueueBufferOutput* output) {
+    if (buffer == nullptr) {
+        return BAD_VALUE;
+    }
+    return queueBuffer(buffer.get(), fd ? fd->get() : -1, output);
+}
+
+status_t Surface::detachBuffer(const sp<GraphicBuffer>& buffer) {
+    if (nullptr == buffer) {
+        return BAD_VALUE;
+    }
+
+    Mutex::Autolock lock(mMutex);
+
+    uint64_t bufferId = buffer->getId();
+    for (int slot = 0; slot < Surface::NUM_BUFFER_SLOTS; ++slot) {
+        auto& bufferSlot = mSlots[slot];
+        if (bufferSlot.buffer != nullptr && bufferSlot.buffer->getId() == bufferId) {
+            bufferSlot.buffer = nullptr;
+            bufferSlot.dirtyRegion = Region::INVALID_REGION;
+            return mGraphicBufferProducer->detachBuffer(slot);
+        }
+    }
+
+    return BAD_VALUE;
+}
+
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
     using DequeueBufferInput = IGraphicBufferProducer::DequeueBufferInput;
     using DequeueBufferOutput = IGraphicBufferProducer::DequeueBufferOutput;
@@ -1118,6 +1194,140 @@
     }
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd,
+                         SurfaceQueueBufferOutput* surfaceOutput) {
+    ATRACE_CALL();
+    ALOGV("Surface::queueBuffer");
+
+    IGraphicBufferProducer::QueueBufferOutput output;
+    IGraphicBufferProducer::QueueBufferInput input;
+    int slot;
+    sp<Fence> fence;
+    {
+        Mutex::Autolock lock(mMutex);
+
+        slot = getSlotFromBufferLocked(buffer);
+        if (slot < 0) {
+            if (fenceFd >= 0) {
+                close(fenceFd);
+            }
+            return slot;
+        }
+        if (mSharedBufferSlot == slot && mSharedBufferHasBeenQueued) {
+            if (fenceFd >= 0) {
+                close(fenceFd);
+            }
+            return OK;
+        }
+
+        getQueueBufferInputLocked(buffer, fenceFd, mTimestamp, &input);
+        applyGrallocMetadataLocked(buffer, input);
+        fence = input.fence;
+    }
+    nsecs_t now = systemTime();
+    // Drop the lock temporarily while we touch the underlying producer. In the case of a local
+    // BufferQueue, the following should be allowable:
+    //
+    //    Surface::queueBuffer
+    // -> IConsumerListener::onFrameAvailable callback triggers automatically
+    // ->   implementation calls IGraphicBufferConsumer::acquire/release immediately
+    // -> SurfaceListener::onBufferRelesed callback triggers automatically
+    // ->   implementation calls Surface::dequeueBuffer
+    status_t err = mGraphicBufferProducer->queueBuffer(slot, input, &output);
+    {
+        Mutex::Autolock lock(mMutex);
+
+        mLastQueueDuration = systemTime() - now;
+        if (err != OK) {
+            ALOGE("queueBuffer: error queuing buffer, %d", err);
+        }
+
+        onBufferQueuedLocked(slot, fence, output);
+    }
+
+    if (surfaceOutput != nullptr) {
+        *surfaceOutput = {.bufferReplaced = output.bufferReplaced};
+    }
+
+    return err;
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+int Surface::queueBuffers(const std::vector<BatchQueuedBuffer>& buffers,
+                          std::vector<SurfaceQueueBufferOutput>* queueBufferOutputs)
+#else
+int Surface::queueBuffers(const std::vector<BatchQueuedBuffer>& buffers)
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+{
+    ATRACE_CALL();
+    ALOGV("Surface::queueBuffers");
+
+    size_t numBuffers = buffers.size();
+    std::vector<IGraphicBufferProducer::QueueBufferInput> igbpQueueBufferInputs(numBuffers);
+    std::vector<IGraphicBufferProducer::QueueBufferOutput> igbpQueueBufferOutputs;
+    std::vector<int> bufferSlots(numBuffers, -1);
+    std::vector<sp<Fence>> bufferFences(numBuffers);
+
+    int err;
+    {
+        Mutex::Autolock lock(mMutex);
+
+        if (mSharedBufferMode) {
+            ALOGE("%s: batched operation is not supported in shared buffer mode", __FUNCTION__);
+            return INVALID_OPERATION;
+        }
+
+        for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+            int i = getSlotFromBufferLocked(buffers[batchIdx].buffer);
+            if (i < 0) {
+                if (buffers[batchIdx].fenceFd >= 0) {
+                    close(buffers[batchIdx].fenceFd);
+                }
+                return i;
+            }
+            bufferSlots[batchIdx] = i;
+
+            IGraphicBufferProducer::QueueBufferInput input;
+            getQueueBufferInputLocked(buffers[batchIdx].buffer, buffers[batchIdx].fenceFd,
+                                      buffers[batchIdx].timestamp, &input);
+            input.slot = i;
+            bufferFences[batchIdx] = input.fence;
+            igbpQueueBufferInputs[batchIdx] = input;
+        }
+    }
+    nsecs_t now = systemTime();
+    err = mGraphicBufferProducer->queueBuffers(igbpQueueBufferInputs, &igbpQueueBufferOutputs);
+    {
+        Mutex::Autolock lock(mMutex);
+        mLastQueueDuration = systemTime() - now;
+        if (err != OK) {
+            ALOGE("%s: error queuing buffer, %d", __FUNCTION__, err);
+        }
+
+        for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+            onBufferQueuedLocked(bufferSlots[batchIdx], bufferFences[batchIdx],
+                                 igbpQueueBufferOutputs[batchIdx]);
+        }
+    }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    if (queueBufferOutputs != nullptr) {
+        queueBufferOutputs->clear();
+        queueBufferOutputs->resize(numBuffers);
+        for (size_t batchIdx = 0; batchIdx < numBuffers; batchIdx++) {
+            (*queueBufferOutputs)[batchIdx].bufferReplaced =
+                    igbpQueueBufferOutputs[batchIdx].bufferReplaced;
+        }
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+    return err;
+}
+
+#else
+
 int Surface::queueBuffer(android_native_buffer_t* buffer, int fenceFd) {
     ATRACE_CALL();
     ALOGV("Surface::queueBuffer");
@@ -1205,6 +1415,8 @@
     return err;
 }
 
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 void Surface::querySupportedTimestampsLocked() const {
     // mMutex must be locked when calling this method.
 
@@ -1475,6 +1687,9 @@
     case NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO:
         res = dispatchSetFrameTimelineInfo(args);
         break;
+    case NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS:
+        res = dispatchSetAdditionalOptions(args);
+        break;
     default:
         res = NAME_NOT_FOUND;
         break;
@@ -1833,36 +2048,47 @@
     return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
 }
 
+int Surface::dispatchSetAdditionalOptions(va_list args) {
+    ATRACE_CALL();
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    const AHardwareBufferLongOptions* opts = va_arg(args, const AHardwareBufferLongOptions*);
+    const size_t optsSize = va_arg(args, size_t);
+    std::vector<gui::AdditionalOptions> convertedOpts;
+    convertedOpts.reserve(optsSize);
+    for (size_t i = 0; i < optsSize; i++) {
+        convertedOpts.emplace_back(opts[i].name, opts[i].value);
+    }
+    return setAdditionalOptions(convertedOpts);
+#else
+    (void)args;
+    return INVALID_OPERATION;
+#endif
+}
+
 bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
 }
 
 int Surface::connect(int api) {
-    static sp<IProducerListener> listener = new StubProducerListener();
+    static sp<SurfaceListener> listener = new StubSurfaceListener();
     return connect(api, listener);
 }
 
-int Surface::connect(int api, const sp<IProducerListener>& listener) {
-    return connect(api, listener, false);
-}
-
-int Surface::connect(
-        int api, bool reportBufferRemoval, const sp<SurfaceListener>& sListener) {
-    if (sListener != nullptr) {
-        mListenerProxy = new ProducerListenerProxy(this, sListener);
-    }
-    return connect(api, mListenerProxy, reportBufferRemoval);
-}
-
-int Surface::connect(
-        int api, const sp<IProducerListener>& listener, bool reportBufferRemoval) {
+int Surface::connect(int api, const sp<SurfaceListener>& listener, bool reportBufferRemoval) {
     ATRACE_CALL();
     ALOGV("Surface::connect");
     Mutex::Autolock lock(mMutex);
     IGraphicBufferProducer::QueueBufferOutput output;
     mReportRemovedBuffers = reportBufferRemoval;
-    int err = mGraphicBufferProducer->connect(listener, api, mProducerControlledByApp, &output);
+
+    if (listener != nullptr) {
+        mListenerProxy = new ProducerListenerProxy(this, listener);
+    }
+
+    int err =
+            mGraphicBufferProducer->connect(mListenerProxy, api, mProducerControlledByApp, &output);
     if (err == NO_ERROR) {
         mDefaultWidth = output.width;
         mDefaultHeight = output.height;
@@ -1877,6 +2103,13 @@
         }
 
         mConsumerRunningBehind = (output.numPendingBuffers >= 2);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+        if (listener && listener->needsDeathNotify()) {
+            mSurfaceDeathListener = sp<ProducerDeathListenerProxy>::make(listener);
+            IInterface::asBinder(mGraphicBufferProducer)->linkToDeath(mSurfaceDeathListener);
+        }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
     }
     if (!err && api == NATIVE_WINDOW_API_CPU) {
         mConnectedToCpu = true;
@@ -1890,7 +2123,6 @@
     return err;
 }
 
-
 int Surface::disconnect(int api, IGraphicBufferProducer::DisconnectMode mode) {
     ATRACE_CALL();
     ALOGV("Surface::disconnect");
@@ -1918,6 +2150,14 @@
             mConnectedToCpu = false;
         }
     }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    if (mSurfaceDeathListener != nullptr) {
+        IInterface::asBinder(mGraphicBufferProducer)->unlinkToDeath(mSurfaceDeathListener);
+        mSurfaceDeathListener = nullptr;
+    }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
     return err;
 }
 
@@ -2619,6 +2859,17 @@
     return BAD_VALUE;
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+status_t Surface::setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) {
+    if (!GraphicBufferAllocator::get().supportsAdditionalOptions()) {
+        return INVALID_OPERATION;
+    }
+
+    Mutex::Autolock lock(mMutex);
+    return mGraphicBufferProducer->setAdditionalOptions(options);
+}
+#endif
+
 sp<IBinder> Surface::getSurfaceControlHandle() const {
     Mutex::Autolock lock(mMutex);
     return mSurfaceControlHandle;
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 7f64a04..df58df4 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -20,8 +20,11 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/EdgeExtensionParameters.h>
 #include <android/gui/ISurfaceComposerClient.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/TrustedPresentationThresholds.h>
@@ -40,7 +43,7 @@
 
 #include <system/graphics.h>
 
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/CpuConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
@@ -86,9 +89,12 @@
     return (((int64_t)getpid()) << 32) | ++idCounter;
 }
 
-void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
+constexpr int64_t INVALID_VSYNC = -1;
+
 } // namespace
 
+const std::string SurfaceComposerClient::kEmpty{};
+
 ComposerService::ComposerService()
 : Singleton<ComposerService>() {
     Mutex::Autolock _l(mLock);
@@ -205,9 +211,168 @@
     return DefaultComposerClient::getComposerClient();
 }
 
+// ---------------------------------------------------------------------------
+
 JankDataListener::~JankDataListener() {
 }
 
+status_t JankDataListener::flushJankData() {
+    if (mLayerId == -1) {
+        return INVALID_OPERATION;
+    }
+
+    binder::Status status = ComposerServiceAIDL::getComposerService()->flushJankData(mLayerId);
+    return statusTFromBinderStatus(status);
+}
+
+std::mutex JankDataListenerFanOut::sFanoutInstanceMutex;
+std::unordered_map<int32_t, sp<JankDataListenerFanOut>> JankDataListenerFanOut::sFanoutInstances;
+
+binder::Status JankDataListenerFanOut::onJankData(const std::vector<gui::JankData>& jankData) {
+    // Find the highest VSync ID.
+    int64_t lastVsync = jankData.empty()
+            ? 0
+            : std::max_element(jankData.begin(), jankData.end(),
+                               [](const gui::JankData& jd1, const gui::JankData& jd2) {
+                                   return jd1.frameVsyncId < jd2.frameVsyncId;
+                               })
+                      ->frameVsyncId;
+
+    // Fan out the jank data callback.
+    std::vector<wp<JankDataListener>> listenersToRemove;
+    for (auto listener : getActiveListeners()) {
+        if (!listener->onJankDataAvailable(jankData) ||
+            (listener->mRemoveAfter >= 0 && listener->mRemoveAfter <= lastVsync)) {
+            listenersToRemove.push_back(listener);
+        }
+    }
+
+    return removeListeners(listenersToRemove)
+            ? binder::Status::ok()
+            : binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER);
+}
+
+status_t JankDataListenerFanOut::addListener(sp<SurfaceControl> sc, sp<JankDataListener> listener) {
+    sp<IBinder> layer = sc->getHandle();
+    if (layer == nullptr) {
+        return UNEXPECTED_NULL;
+    }
+    int32_t layerId = sc->getLayerId();
+
+    sFanoutInstanceMutex.lock();
+    auto it = sFanoutInstances.find(layerId);
+    bool registerNeeded = it == sFanoutInstances.end();
+    sp<JankDataListenerFanOut> fanout;
+    if (registerNeeded) {
+        fanout = sp<JankDataListenerFanOut>::make(layerId);
+        sFanoutInstances.insert({layerId, fanout});
+    } else {
+        fanout = it->second;
+    }
+
+    fanout->mMutex.lock();
+    fanout->mListeners.insert(listener);
+    fanout->mMutex.unlock();
+
+    sFanoutInstanceMutex.unlock();
+
+    if (registerNeeded) {
+        binder::Status status =
+                ComposerServiceAIDL::getComposerService()->addJankListener(layer, fanout);
+        return statusTFromBinderStatus(status);
+    }
+    return OK;
+}
+
+status_t JankDataListenerFanOut::removeListener(sp<JankDataListener> listener) {
+    int32_t layerId = listener->mLayerId;
+    if (layerId == -1) {
+        return INVALID_OPERATION;
+    }
+
+    int64_t removeAfter = INVALID_VSYNC;
+    sp<JankDataListenerFanOut> fanout;
+
+    sFanoutInstanceMutex.lock();
+    auto it = sFanoutInstances.find(layerId);
+    if (it != sFanoutInstances.end()) {
+        fanout = it->second;
+        removeAfter = fanout->updateAndGetRemovalVSync();
+    }
+
+    if (removeAfter != INVALID_VSYNC) {
+        // Remove this instance from the map, so that no new listeners are added
+        // while we're scheduled to be removed.
+        sFanoutInstances.erase(layerId);
+    }
+    sFanoutInstanceMutex.unlock();
+
+    if (removeAfter < 0) {
+        return OK;
+    }
+
+    binder::Status status =
+            ComposerServiceAIDL::getComposerService()->removeJankListener(layerId, fanout,
+                                                                          removeAfter);
+    return statusTFromBinderStatus(status);
+}
+
+std::vector<sp<JankDataListener>> JankDataListenerFanOut::getActiveListeners() {
+    std::scoped_lock<std::mutex> lock(mMutex);
+
+    std::vector<sp<JankDataListener>> listeners;
+    for (auto it = mListeners.begin(); it != mListeners.end();) {
+        auto listener = it->promote();
+        if (!listener) {
+            it = mListeners.erase(it);
+        } else {
+            listeners.push_back(std::move(listener));
+            it++;
+        }
+    }
+    return listeners;
+}
+
+bool JankDataListenerFanOut::removeListeners(const std::vector<wp<JankDataListener>>& listeners) {
+    std::scoped_lock<std::mutex> fanoutLock(sFanoutInstanceMutex);
+    std::scoped_lock<std::mutex> listenersLock(mMutex);
+
+    for (auto listener : listeners) {
+        mListeners.erase(listener);
+    }
+
+    if (mListeners.empty()) {
+        sFanoutInstances.erase(mLayerId);
+        return false;
+    }
+    return true;
+}
+
+int64_t JankDataListenerFanOut::updateAndGetRemovalVSync() {
+    std::scoped_lock<std::mutex> lock(mMutex);
+    if (mRemoveAfter >= 0) {
+        // We've already been scheduled to be removed. Don't schedule again.
+        return INVALID_VSYNC;
+    }
+
+    int64_t removeAfter = 0;
+    for (auto it = mListeners.begin(); it != mListeners.end();) {
+        auto listener = it->promote();
+        if (!listener) {
+            it = mListeners.erase(it);
+        } else if (listener->mRemoveAfter < 0) {
+            // We have at least one listener that's still interested. Don't remove.
+            return INVALID_VSYNC;
+        } else {
+            removeAfter = std::max(removeAfter, listener->mRemoveAfter);
+            it++;
+        }
+    }
+
+    mRemoveAfter = removeAfter;
+    return removeAfter;
+}
+
 // ---------------------------------------------------------------------------
 
 // TransactionCompletedListener does not use ANDROID_SINGLETON_STATIC_INSTANCE because it needs
@@ -254,14 +419,6 @@
                 surfaceControls,
         CallbackId::Type callbackType) {
     std::lock_guard<std::mutex> lock(mMutex);
-    return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType);
-}
-
-CallbackId TransactionCompletedListener::addCallbackFunctionLocked(
-        const TransactionCompletedCallback& callbackFunction,
-        const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
-                surfaceControls,
-        CallbackId::Type callbackType) {
     startListeningLocked();
 
     CallbackId callbackId(getNextIdLocked(), callbackType);
@@ -270,33 +427,11 @@
 
     for (const auto& surfaceControl : surfaceControls) {
         callbackSurfaceControls[surfaceControl->getHandle()] = surfaceControl;
-
-        if (callbackType == CallbackId::Type::ON_COMPLETE &&
-            mJankListeners.count(surfaceControl->getLayerId()) != 0) {
-            callbackId.includeJankData = true;
-        }
     }
 
     return callbackId;
 }
 
-void TransactionCompletedListener::addJankListener(const sp<JankDataListener>& listener,
-                                                   sp<SurfaceControl> surfaceControl) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    mJankListeners.insert({surfaceControl->getLayerId(), listener});
-}
-
-void TransactionCompletedListener::removeJankListener(const sp<JankDataListener>& listener) {
-    std::lock_guard<std::mutex> lock(mMutex);
-    for (auto it = mJankListeners.begin(); it != mJankListeners.end();) {
-        if (it->second == listener) {
-            it = mJankListeners.erase(it);
-        } else {
-            it++;
-        }
-    }
-}
-
 void TransactionCompletedListener::setReleaseBufferCallback(const ReleaseCallbackId& callbackId,
                                                             ReleaseBufferCallback listener) {
     std::scoped_lock<std::mutex> lock(mMutex);
@@ -323,32 +458,20 @@
 }
 
 void TransactionCompletedListener::addSurfaceControlToCallbacks(
-        SurfaceComposerClient::CallbackInfo& callbackInfo,
-        const sp<SurfaceControl>& surfaceControl) {
+        const sp<SurfaceControl>& surfaceControl,
+        const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds) {
     std::lock_guard<std::mutex> lock(mMutex);
 
-    bool includingJankData = false;
-    for (auto callbackId : callbackInfo.callbackIds) {
+    for (auto callbackId : callbackIds) {
         mCallbacks[callbackId].surfaceControls.emplace(std::piecewise_construct,
                                                        std::forward_as_tuple(
                                                                surfaceControl->getHandle()),
                                                        std::forward_as_tuple(surfaceControl));
-        includingJankData = includingJankData || callbackId.includeJankData;
-    }
-
-    // If no registered callback is requesting jank data, but there is a jank listener registered
-    // on the new surface control, add a synthetic callback that requests the jank data.
-    if (!includingJankData && mJankListeners.count(surfaceControl->getLayerId()) != 0) {
-        CallbackId callbackId =
-                addCallbackFunctionLocked(&emptyCallback, callbackInfo.surfaceControls,
-                                          CallbackId::Type::ON_COMPLETE);
-        callbackInfo.callbackIds.emplace(callbackId);
     }
 }
 
 void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> callbacksMap;
-    std::multimap<int32_t, sp<JankDataListener>> jankListenersMap;
     {
         std::lock_guard<std::mutex> lock(mMutex);
 
@@ -364,7 +487,6 @@
          * sp<SurfaceControl> that could possibly exist for the callbacks.
          */
         callbacksMap = mCallbacks;
-        jankListenersMap = mJankListeners;
         for (const auto& transactionStats : listenerStats.transactionStats) {
             for (auto& callbackId : transactionStats.callbackIds) {
                 mCallbacks.erase(callbackId);
@@ -484,12 +606,6 @@
                         transactionStats.presentFence, surfaceStats);
                 }
             }
-
-            if (surfaceStats.jankData.empty()) continue;
-            auto jankRange = jankListenersMap.equal_range(layerId);
-            for (auto it = jankRange.first; it != jankRange.second; it++) {
-                it->second->onJankDataAvailable(surfaceStats.jankData);
-            }
         }
     }
 }
@@ -706,11 +822,11 @@
 
 SurfaceComposerClient::Transaction::Transaction() {
     mId = generateId();
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 SurfaceComposerClient::Transaction::Transaction(const Transaction& other)
       : mId(other.mId),
-        mTransactionNestCount(other.mTransactionNestCount),
         mAnimation(other.mAnimation),
         mEarlyWakeupStart(other.mEarlyWakeupStart),
         mEarlyWakeupEnd(other.mEarlyWakeupEnd),
@@ -723,6 +839,7 @@
     mComposerStates = other.mComposerStates;
     mInputWindowCommands = other.mInputWindowCommands;
     mListenerCallbacks = other.mListenerCallbacks;
+    mTransactionCompletedListener = TransactionCompletedListener::getInstance();
 }
 
 void SurfaceComposerClient::Transaction::sanitize(int pid, int uid) {
@@ -749,7 +866,6 @@
 
 status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel) {
     const uint64_t transactionId = parcel->readUint64();
-    const uint32_t transactionNestCount = parcel->readUint32();
     const bool animation = parcel->readBool();
     const bool earlyWakeupStart = parcel->readBool();
     const bool earlyWakeupEnd = parcel->readBool();
@@ -846,7 +962,6 @@
 
     // Parsing was successful. Update the object.
     mId = transactionId;
-    mTransactionNestCount = transactionNestCount;
     mAnimation = animation;
     mEarlyWakeupStart = earlyWakeupStart;
     mEarlyWakeupEnd = earlyWakeupEnd;
@@ -878,7 +993,6 @@
     const_cast<SurfaceComposerClient::Transaction*>(this)->cacheBuffers();
 
     parcel->writeUint64(mId);
-    parcel->writeUint32(mTransactionNestCount);
     parcel->writeBool(mAnimation);
     parcel->writeBool(mEarlyWakeupStart);
     parcel->writeBool(mEarlyWakeupEnd);
@@ -1000,8 +1114,9 @@
 
         // register all surface controls for all callbackIds for this listener that is merging
         for (const auto& surfaceControl : currentProcessCallbackInfo.surfaceControls) {
-            TransactionCompletedListener::getInstance()
-                    ->addSurfaceControlToCallbacks(currentProcessCallbackInfo, surfaceControl);
+            mTransactionCompletedListener
+                    ->addSurfaceControlToCallbacks(surfaceControl,
+                                                   currentProcessCallbackInfo.callbackIds);
         }
     }
 
@@ -1029,7 +1144,6 @@
     mInputWindowCommands.clear();
     mUncacheBuffers.clear();
     mMayContainBuffer = false;
-    mTransactionNestCount = 0;
     mAnimation = false;
     mEarlyWakeupStart = false;
     mEarlyWakeupEnd = false;
@@ -1055,7 +1169,8 @@
     uncacheBuffer.token = BufferCache::getInstance().getToken();
     uncacheBuffer.id = cacheId;
     Vector<ComposerState> composerStates;
-    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, {},
+    Vector<DisplayState> displayStates;
+    status_t status = sf->setTransactionState(FrameTimelineInfo{}, composerStates, displayStates,
                                               ISurfaceComposer::eOneWay,
                                               Transaction::getDefaultApplyToken(), {}, systemTime(),
                                               true, {uncacheBuffer}, false, {}, generateId(), {});
@@ -1276,19 +1391,22 @@
 }
 // ---------------------------------------------------------------------------
 
-sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure,
-                                                 float requestedRefereshRate) {
+sp<IBinder> SurfaceComposerClient::createVirtualDisplay(const std::string& displayName,
+                                                        bool isSecure, const std::string& uniqueId,
+                                                        float requestedRefreshRate) {
     sp<IBinder> display = nullptr;
     binder::Status status =
-            ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
-                                                                             displayName.c_str()),
-                                                                     secure, requestedRefereshRate,
-                                                                     &display);
+            ComposerServiceAIDL::getComposerService()->createVirtualDisplay(displayName, isSecure,
+                                                                            uniqueId,
+                                                                            requestedRefreshRate,
+                                                                            &display);
     return status.isOk() ? display : nullptr;
 }
 
-void SurfaceComposerClient::destroyDisplay(const sp<IBinder>& display) {
-    ComposerServiceAIDL::getComposerService()->destroyDisplay(display);
+status_t SurfaceComposerClient::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
+    return ComposerServiceAIDL::getComposerService()
+            ->destroyVirtualDisplay(displayToken)
+            .transactionError();
 }
 
 std::vector<PhysicalDisplayId> SurfaceComposerClient::getPhysicalDisplayIds() {
@@ -1354,7 +1472,7 @@
     auto& callbackInfo = mListenerCallbacks[TransactionCompletedListener::getIInstance()];
     callbackInfo.surfaceControls.insert(sc);
 
-    TransactionCompletedListener::getInstance()->addSurfaceControlToCallbacks(callbackInfo, sc);
+    mTransactionCompletedListener->addSurfaceControlToCallbacks(sc, callbackInfo.callbackIds);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setPosition(
@@ -1672,7 +1790,7 @@
 
     std::shared_ptr<BufferData> bufferData = std::move(s->bufferData);
 
-    TransactionCompletedListener::getInstance()->removeReleaseBufferCallback(
+    mTransactionCompletedListener->removeReleaseBufferCallback(
             bufferData->generateReleaseCallbackId());
     s->what &= ~layer_state_t::eBufferChanged;
     s->bufferData = nullptr;
@@ -1695,7 +1813,7 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
         const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
         const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
-        uint32_t producerId, ReleaseBufferCallback callback) {
+        uint32_t producerId, ReleaseBufferCallback callback, nsecs_t dequeueTime) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -1711,12 +1829,12 @@
         bufferData->frameNumber = frameNumber;
         bufferData->producerId = producerId;
         bufferData->flags |= BufferData::BufferDataChange::frameNumberChanged;
+        bufferData->dequeueTime = dequeueTime;
         if (fence) {
             bufferData->acquireFence = *fence;
             bufferData->flags |= BufferData::BufferDataChange::fenceChanged;
         }
-        bufferData->releaseBufferEndpoint =
-                IInterface::asBinder(TransactionCompletedListener::getIInstance());
+        bufferData->releaseBufferEndpoint = IInterface::asBinder(mTransactionCompletedListener);
         setReleaseBufferCallback(bufferData.get(), callback);
     }
 
@@ -1774,9 +1892,10 @@
         return;
     }
 
-    bufferData->releaseBufferListener = TransactionCompletedListener::getIInstance();
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(), callback);
+    bufferData->releaseBufferListener =
+            static_cast<sp<ITransactionCompletedListener>>(mTransactionCompletedListener);
+    mTransactionCompletedListener->setReleaseBufferCallback(bufferData->generateReleaseCallbackId(),
+                                                            callback);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDataspace(
@@ -1932,31 +2051,31 @@
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::addTransactionCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext,
         CallbackId::Type callbackType) {
-    auto listener = TransactionCompletedListener::getInstance();
-
-    auto callbackWithContext = std::bind(callback, callbackContext, std::placeholders::_1,
-                                         std::placeholders::_2, std::placeholders::_3);
-    const auto& surfaceControls =
-            mListenerCallbacks[TransactionCompletedListener::getIInstance()].surfaceControls;
+    auto callbackWithContext =
+            std::bind(std::move(callback), callbackContext, std::placeholders::_1,
+                      std::placeholders::_2, std::placeholders::_3);
+    const auto& surfaceControls = mListenerCallbacks[mTransactionCompletedListener].surfaceControls;
 
     CallbackId callbackId =
-            listener->addCallbackFunction(callbackWithContext, surfaceControls, callbackType);
+            mTransactionCompletedListener->addCallbackFunction(callbackWithContext, surfaceControls,
+                                                               callbackType);
 
-    mListenerCallbacks[TransactionCompletedListener::getIInstance()].callbackIds.emplace(
-            callbackId);
+    mListenerCallbacks[mTransactionCompletedListener].callbackIds.emplace(callbackId);
     return *this;
 }
 
 SurfaceComposerClient::Transaction&
 SurfaceComposerClient::Transaction::addTransactionCompletedCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext) {
-    return addTransactionCallback(callback, callbackContext, CallbackId::Type::ON_COMPLETE);
+    return addTransactionCallback(std::move(callback), callbackContext,
+                                  CallbackId::Type::ON_COMPLETE);
 }
 
 SurfaceComposerClient::Transaction&
 SurfaceComposerClient::Transaction::addTransactionCommittedCallback(
         TransactionCompletedCallbackTakesContext callback, void* callbackContext) {
-    return addTransactionCallback(callback, callbackContext, CallbackId::Type::ON_COMMIT);
+    return addTransactionCallback(std::move(callback), callbackContext,
+                                  CallbackId::Type::ON_COMMIT);
 }
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::notifyProducerDisconnect(
@@ -2175,6 +2294,13 @@
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay(
         const sp<SurfaceControl>& sc, bool isTrustedOverlay) {
+    return setTrustedOverlay(sc,
+                             isTrustedOverlay ? gui::TrustedOverlay::ENABLED
+                                              : gui::TrustedOverlay::UNSET);
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setTrustedOverlay(
+        const sp<SurfaceControl>& sc, gui::TrustedOverlay trustedOverlay) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
@@ -2182,7 +2308,7 @@
     }
 
     s->what |= layer_state_t::eTrustedOverlayChanged;
-    s->isTrustedOverlay = isTrustedOverlay;
+    s->trustedOverlay = trustedOverlay;
     return *this;
 }
 
@@ -2205,6 +2331,23 @@
     return *this;
 }
 
+bool SurfaceComposerClient::flagEdgeExtensionEffectUseShader() {
+    return com::android::graphics::libgui::flags::edge_extension_shader();
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setEdgeExtensionEffect(
+        const sp<SurfaceControl>& sc, const gui::EdgeExtensionParameters& effect) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    s->what |= layer_state_t::eEdgeExtensionChanged;
+    s->edgeExtensionParameters = effect;
+    return *this;
+}
+
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferCrop(
         const sp<SurfaceControl>& sc, const Rect& bufferCrop) {
     layer_state_t* s = getLayerState(sc);
@@ -2250,18 +2393,17 @@
     return *this;
 }
 
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder(
-        const sp<SurfaceControl>& sc, bool shouldEnable, float width, const half4& color) {
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBufferReleaseChannel(
+        const sp<SurfaceControl>& sc,
+        const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
         return *this;
     }
 
-    s->what |= layer_state_t::eRenderBorderChanged;
-    s->borderEnabled = shouldEnable;
-    s->borderWidth = width;
-    s->borderColor = color;
+    s->what |= layer_state_t::eBufferReleaseChannelChanged;
+    s->bufferReleaseChannel = channel;
 
     registerSurfaceControlForCallback(sc);
     return *this;
@@ -2350,8 +2492,9 @@
         const sp<SurfaceControl>& sc, TrustedPresentationCallback cb,
         const TrustedPresentationThresholds& thresholds, void* context,
         sp<SurfaceComposerClient::PresentationCallbackRAII>& outCallbackRef) {
-    auto listener = TransactionCompletedListener::getInstance();
-    outCallbackRef = listener->addTrustedPresentationCallback(cb, sc->getLayerId(), context);
+    outCallbackRef =
+            mTransactionCompletedListener->addTrustedPresentationCallback(cb, sc->getLayerId(),
+                                                                          context);
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -2368,8 +2511,7 @@
 
 SurfaceComposerClient::Transaction&
 SurfaceComposerClient::Transaction::clearTrustedPresentationCallback(const sp<SurfaceControl>& sc) {
-    auto listener = TransactionCompletedListener::getInstance();
-    listener->clearTrustedPresentationCallback(sc->getLayerId());
+    mTransactionCompletedListener->clearTrustedPresentationCallback(sc->getLayerId());
 
     layer_state_t* s = getLayerState(sc);
     if (!s) {
@@ -3131,6 +3273,10 @@
             ->removeWindowInfosListener(windowInfosListener,
                                         ComposerServiceAIDL::getComposerService());
 }
+
+void SurfaceComposerClient::notifyShutdown() {
+    ComposerServiceAIDL::getComposerService()->notifyShutdown();
+}
 // ----------------------------------------------------------------------------
 
 status_t ScreenshotClient::captureDisplay(const DisplayCaptureArgs& captureArgs,
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index c5f9c38..f126c0b 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -139,9 +139,9 @@
     uint32_t ignore;
     auto flags = mCreateFlags & (ISurfaceComposerClient::eCursorWindow |
                                  ISurfaceComposerClient::eOpaque);
-    mBbqChild = mClient->createSurface(String8("bbq-wrapper"), 0, 0, mFormat,
+    mBbqChild = mClient->createSurface(String8::format("[BBQ] %s", mName.c_str()), 0, 0, mFormat,
                                        flags, mHandle, {}, &ignore);
-    mBbq = sp<BLASTBufferQueue>::make("bbq-adapter", mBbqChild, mWidth, mHeight, mFormat);
+    mBbq = sp<BLASTBufferQueue>::make("[BBQ]" + mName, mBbqChild, mWidth, mHeight, mFormat);
 
     // This surface is always consumed by SurfaceFlinger, so the
     // producerControlledByApp value doesn't matter; using false.
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index 86bf0ee..82d2554 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -73,14 +73,6 @@
     touchableRegion.orSelf(region);
 }
 
-bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
-    return touchableRegion.contains(x, y);
-}
-
-bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
-    return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
-}
-
 bool WindowInfo::supportsSplitTouch() const {
     return !inputConfig.test(InputConfig::PREVENT_SPLITTING);
 }
@@ -154,7 +146,7 @@
         parcel->writeInt32(ownerUid.val()) ?:
         parcel->writeUtf8AsUtf16(packageName) ?:
         parcel->writeInt32(inputConfig.get()) ?:
-        parcel->writeInt32(displayId) ?:
+        parcel->writeInt32(displayId.val()) ?:
         applicationInfo.writeToParcel(parcel) ?:
         parcel->write(touchableRegion) ?:
         parcel->writeBool(replaceTouchableRegionWithCrop) ?:
@@ -183,7 +175,8 @@
     }
 
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
-    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt;
+    int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt,
+            displayIdInt;
     sp<IBinder> touchableRegionCropHandleSp;
 
     // clang-format off
@@ -206,7 +199,7 @@
         parcel->readInt32(&ownerUidInt) ?:
         parcel->readUtf8FromUtf16(&packageName) ?:
         parcel->readInt32(&inputConfigInt) ?:
-        parcel->readInt32(&displayId) ?:
+        parcel->readInt32(&displayIdInt) ?:
         applicationInfo.readFromParcel(parcel) ?:
         parcel->read(touchableRegion) ?:
         parcel->readBool(&replaceTouchableRegionWithCrop) ?:
@@ -229,6 +222,7 @@
     ownerPid = Pid{ownerPidInt};
     ownerUid = Uid{static_cast<uid_t>(ownerUidInt)};
     touchableRegionCropHandle = touchableRegionCropHandleSp;
+    displayId = ui::LogicalDisplayId{displayIdInt};
 
     return OK;
 }
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index 0929b8e..91c9a85 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -15,7 +15,7 @@
  */
 
 #include <android/gui/ISurfaceComposer.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/WindowInfosListenerReporter.h>
 #include "gui/WindowInfosUpdate.h"
 
diff --git a/libs/gui/aidl/Android.bp b/libs/gui/aidl/Android.bp
new file mode 100644
index 0000000..fd035f6
--- /dev/null
+++ b/libs/gui/aidl/Android.bp
@@ -0,0 +1,82 @@
+// Copyright 2024 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"],
+    default_team: "trendy_team_android_core_graphics_stack",
+}
+
+filegroup {
+    name: "libgui_unstructured_aidl_files",
+    srcs: [
+        ":libgui_extra_unstructured_aidl_files",
+
+        "android/gui/BitTube.aidl",
+        "android/gui/LayerMetadata.aidl",
+        "android/gui/ParcelableVsyncEventData.aidl",
+        "android/gui/ScreenCaptureResults.aidl",
+    ],
+}
+
+aidl_library {
+    name: "libgui_unstructured_aidl",
+    hdrs: [":libgui_unstructured_aidl_files"],
+}
+
+filegroup {
+    name: "libgui_interface_aidl_files",
+    srcs: [
+        ":libgui_extra_aidl_files",
+        "**/*.aidl",
+    ],
+    exclude_srcs: [":libgui_unstructured_aidl_files"],
+}
+
+aidl_interface {
+    name: "android.gui",
+    unstable: true,
+    srcs: [
+        ":libgui_interface_aidl_files",
+    ],
+    include_dirs: [
+        "frameworks/native/libs/gui",
+        "frameworks/native/libs/gui/aidl",
+    ],
+    headers: [
+        "libgui_aidl_hdrs",
+        "libgui_extra_unstructured_aidl_hdrs",
+    ],
+    backend: {
+        rust: {
+            enabled: true,
+            additional_rustlibs: [
+                "libgui_aidl_types_rs",
+            ],
+        },
+        java: {
+            enabled: false,
+        },
+        cpp: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+    },
+}
diff --git a/libs/gui/aidl/android/gui/BitTube.aidl b/libs/gui/aidl/android/gui/BitTube.aidl
index 6b0595e..eb231c1 100644
--- a/libs/gui/aidl/android/gui/BitTube.aidl
+++ b/libs/gui/aidl/android/gui/BitTube.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable BitTube cpp_header "private/gui/BitTube.h";
+parcelable BitTube cpp_header "private/gui/BitTube.h" rust_type "gui_aidl_types_rs::BitTube";
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 920d949..4920344 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -16,4 +16,63 @@
 
 package android.gui;
 
-parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+import android.gui.ARect;
+
+// Common arguments for capturing content on-screen
+parcelable CaptureArgs {
+    const int UNSET_UID = -1;
+
+    // Desired pixel format of the final screenshotted buffer
+    int /*ui::PixelFormat*/ pixelFormat = 1;
+
+    // Crop in layer space: all content outside of the crop will not be captured.
+    ARect sourceCrop;
+
+    // Scale in the x-direction for the screenshotted result.
+    float frameScaleX = 1.0f;
+
+    // Scale in the y-direction for the screenshotted result.
+    float frameScaleY = 1.0f;
+
+    // True if capturing secure layers is permitted
+    boolean captureSecureLayers = false;
+
+    // UID whose content we want to screenshot
+    int uid = UNSET_UID;
+
+    // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured
+    // result will be in a colorspace appropriate for capturing the display contents
+    // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be
+    // different from SRGB (byte per color), and failed when checking colors in tests.
+    // NOTE: In normal cases, we want the screen to be captured in display's colorspace.
+    int /*ui::Dataspace*/ dataspace = 0;
+
+    // The receiver of the capture can handle protected buffer. A protected buffer has
+    // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour.
+    // Any read/write access from unprotected context will result in undefined behaviour.
+    // Protected contents are typically DRM contents. This has no direct implication to the
+    // secure property of the surface, which is specified by the application explicitly to avoid
+    // the contents being accessed/captured by screenshot or unsecure display.
+    boolean allowProtected = false;
+
+    // True if the content should be captured in grayscale
+    boolean grayscale = false;
+
+    // List of layers to exclude capturing from
+    IBinder[] excludeHandles;
+
+    // Hint that the caller will use the screenshot animation as part of a transition animation.
+    // The canonical example would be screen rotation - in such a case any color shift in the
+    // screenshot is a detractor so composition in the display's colorspace is required.
+    // Otherwise, the system may choose a colorspace that is more appropriate for use-cases
+    // such as file encoding or for blending HDR content into an ap's UI, where the display's
+    // exact colorspace is not an appropriate intermediate result.
+    // Note that if the caller is requesting a specific dataspace, this hint does nothing.
+    boolean hintForSeamlessTransition = false;
+
+    // Allows the screenshot to attach a gainmap, which allows for a per-pixel
+    // transformation of the screenshot to another luminance range, typically
+    // mapping an SDR base image into HDR.
+    boolean attachGainmap = false;
+}
+
diff --git a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
index 2caa2b9..e00a2df 100644
--- a/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayCaptureArgs.aidl
@@ -16,4 +16,18 @@
 
 package android.gui;
 
-parcelable DisplayCaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
+import android.gui.CaptureArgs;
+
+// Arguments for screenshotting an entire display
+parcelable DisplayCaptureArgs {
+    CaptureArgs captureArgs;
+
+    // The display that we want to screenshot
+    IBinder displayToken;
+
+    // The width of the render area when we screenshot
+    int width = 0;
+    // The length of the render area when we screenshot
+    int height = 0;
+}
+
diff --git a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
index af138c7..13962fe 100644
--- a/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
+++ b/libs/gui/aidl/android/gui/DisplayModeSpecs.aidl
@@ -42,6 +42,17 @@
     }
 
     /**
+     * Refers to the time after which the idle screen's refresh rate is to be reduced
+     */
+    parcelable IdleScreenRefreshRateConfig {
+
+        /**
+         *  The timeout value in milli seconds
+         */
+        int timeoutMillis;
+    }
+
+    /**
      * Base mode ID. This is what system defaults to for all other settings, or
      * if the refresh rate range is not available.
      */
@@ -72,4 +83,13 @@
      * never smaller.
      */
     RefreshRateRanges appRequestRanges;
+
+    /**
+     * The config to represent the maximum time (in ms) for which the display can remain in an idle
+     * state before reducing the refresh rate to conserve power.
+     * Null value refers that the device is not configured to dynamically reduce the refresh rate
+     * based on external conditions.
+     * -1 refers to the current conditions requires no timeout
+     */
+    @nullable IdleScreenRefreshRateConfig idleScreenRefreshRateConfig;
 }
diff --git a/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl b/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl
new file mode 100644
index 0000000..44f4259
--- /dev/null
+++ b/libs/gui/aidl/android/gui/EdgeExtensionParameters.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+
+/** @hide */
+parcelable EdgeExtensionParameters {
+    // These represent the translation of the window as requested by the animation
+    boolean extendRight;
+    boolean extendLeft;
+    boolean extendTop;
+    boolean extendBottom;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/IJankListener.aidl b/libs/gui/aidl/android/gui/IJankListener.aidl
new file mode 100644
index 0000000..2bfd1af
--- /dev/null
+++ b/libs/gui/aidl/android/gui/IJankListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+import android.gui.JankData;
+
+/** @hide */
+interface IJankListener {
+
+  /**
+   * Callback reporting jank data of the most recent frames.
+   * @See {@link ISurfaceComposer#addJankListener(IBinder, IJankListener)}
+   */
+  void onJankData(in JankData[] data);
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index 51e0193..ac14138 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -42,8 +42,8 @@
 import android.gui.ITunnelModeEnabledListener;
 import android.gui.IWindowInfosListener;
 import android.gui.IWindowInfosPublisher;
+import android.gui.IJankListener;
 import android.gui.LayerCaptureArgs;
-import android.gui.LayerDebugInfo;
 import android.gui.OverlayProperties;
 import android.gui.PullAtomData;
 import android.gui.ScreenCaptureResults;
@@ -73,7 +73,7 @@
     void bootFinished();
 
     /**
-     * Create a display event connection
+     * Create a display event connection.
      *
      * layerHandle
      *     Optional binder handle representing a Layer in SF to associate the new
@@ -90,12 +90,14 @@
     @nullable ISurfaceComposerClient createConnection();
 
     /**
-     * Create a virtual display
+     * Create a virtual display.
      *
      * displayName
-     *     The name of the virtual display
-     * secure
-     *     Whether this virtual display is secure
+     *     The name of the virtual display.
+     * isSecure
+     *     Whether this virtual display is secure.
+     * uniqueId
+     *     The unique ID for the display.
      * requestedRefreshRate
      *     The refresh rate, frames per second, to request on the virtual display.
      *     This is just a request, the actual rate may be adjusted to align well
@@ -104,14 +106,14 @@
      *
      * requires ACCESS_SURFACE_FLINGER permission.
      */
-    @nullable IBinder createDisplay(@utf8InCpp String displayName, boolean secure,
-            float requestedRefreshRate);
+    @nullable IBinder createVirtualDisplay(@utf8InCpp String displayName, boolean isSecure,
+            @utf8InCpp String uniqueId, float requestedRefreshRate);
 
     /**
-     * Destroy a virtual display
+     * Destroy a virtual display.
      * requires ACCESS_SURFACE_FLINGER permission.
      */
-    void destroyDisplay(IBinder display);
+    void destroyVirtualDisplay(IBinder displayToken);
 
     /**
      * Get stable IDs for connected physical displays.
@@ -289,13 +291,6 @@
     PullAtomData onPullAtom(int atomId);
 
     /**
-     * Gets the list of active layers in Z order for debugging purposes
-     *
-     * Requires the ACCESS_SURFACE_FLINGER permission.
-     */
-    List<LayerDebugInfo> getLayerDebugInfo();
-
-    /**
      * Gets the composition preference of the default data space and default pixel format,
      * as well as the wide color gamut data space and wide color gamut pixel format.
      * If the wide color gamut data space is V0_SRGB, then it implies that the platform
@@ -579,4 +574,29 @@
     @nullable StalledTransactionInfo getStalledTransactionInfo(int pid);
 
     SchedulingPolicy getSchedulingPolicy();
+
+    /**
+     * Notifies the SurfaceFlinger that the ShutdownThread is running. When it is called,
+     * transaction traces will be captured and writted into a file.
+     * This method should not block the ShutdownThread therefore it's handled asynchronously.
+     */
+    oneway void notifyShutdown();
+
+    /**
+     * Registers the jank listener on the given layer to receive jank data of future frames.
+     */
+    void addJankListener(IBinder layer, IJankListener listener);
+
+    /**
+     * Flushes any pending jank data on the given layer to any registered listeners on that layer.
+     */
+    oneway void flushJankData(int layerId);
+
+    /**
+     * Schedules the removal of the jank listener from the given layer after the VSync with the
+     * specified ID. Use a value <= 0 for afterVsync to remove the listener immediately. The given
+     * listener will not be removed before the given VSync, but may still receive data for frames
+     * past the provided VSync.
+     */
+    oneway void removeJankListener(int layerId, IJankListener listener, long afterVsync);
 }
diff --git a/libs/gui/aidl/android/gui/JankData.aidl b/libs/gui/aidl/android/gui/JankData.aidl
new file mode 100644
index 0000000..ec13681
--- /dev/null
+++ b/libs/gui/aidl/android/gui/JankData.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ package android.gui;
+
+ /** @hide */
+parcelable JankData {
+  /**
+   * Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
+   */
+  long frameVsyncId;
+
+  /**
+   * Bitmask of jank types that occurred.
+   */
+  int jankType;
+
+  /**
+   * Time between frames in nanoseconds.
+   */
+  long frameIntervalNs;
+
+  /**
+   * Time allocated to the application to render this frame.
+   */
+  long scheduledAppFrameTimeNs;
+
+  /**
+   * Time taken by the application to render this frame.
+   */
+  long actualAppFrameTimeNs;
+}
diff --git a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
index f0def50..004c35a 100644
--- a/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/LayerCaptureArgs.aidl
@@ -16,4 +16,15 @@
 
 package android.gui;
 
-parcelable LayerCaptureArgs cpp_header "gui/LayerCaptureArgs.h";
+import android.gui.CaptureArgs;
+
+// Arguments for capturing a layer and/or its children
+parcelable LayerCaptureArgs {
+    CaptureArgs captureArgs;
+
+    // The Layer that we may want to capture. We would also capture its children
+    IBinder layerHandle;
+    // True if we don't actually want to capture the layer and want to capture
+    // its children instead.
+    boolean childrenOnly = false;
+}
diff --git a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl b/libs/gui/aidl/android/gui/LayerDebugInfo.aidl
deleted file mode 100644
index faca980..0000000
--- a/libs/gui/aidl/android/gui/LayerDebugInfo.aidl
+++ /dev/null
@@ -1,19 +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.
- */
-
-package android.gui;
-
-parcelable LayerDebugInfo cpp_header "gui/LayerDebugInfo.h";
diff --git a/libs/gui/aidl/android/gui/LayerMetadata.aidl b/libs/gui/aidl/android/gui/LayerMetadata.aidl
index 1368ac5..d8121be 100644
--- a/libs/gui/aidl/android/gui/LayerMetadata.aidl
+++ b/libs/gui/aidl/android/gui/LayerMetadata.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable LayerMetadata cpp_header "gui/LayerMetadata.h";
+parcelable LayerMetadata cpp_header "gui/LayerMetadata.h" rust_type "gui_aidl_types_rs::LayerMetadata";
diff --git a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
index ba76671..53f443a 100644
--- a/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
+++ b/libs/gui/aidl/android/gui/ParcelableVsyncEventData.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h";
+parcelable ParcelableVsyncEventData cpp_header "gui/VsyncEventData.h" rust_type "gui_aidl_types_rs::VsyncEventData";
diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
index 9908edd..f4ef16d 100644
--- a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
+++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h";
\ No newline at end of file
+parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
diff --git a/libs/gui/android/gui/DisplayInfo.aidl b/libs/gui/android/gui/DisplayInfo.aidl
index 30c0885..3b16724 100644
--- a/libs/gui/android/gui/DisplayInfo.aidl
+++ b/libs/gui/android/gui/DisplayInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable DisplayInfo cpp_header "gui/DisplayInfo.h";
+parcelable DisplayInfo cpp_header "gui/DisplayInfo.h" rust_type "gui_aidl_types_rs::DisplayInfo";
diff --git a/libs/gui/android/gui/TrustedOverlay.aidl b/libs/gui/android/gui/TrustedOverlay.aidl
new file mode 100644
index 0000000..06fb5f0
--- /dev/null
+++ b/libs/gui/android/gui/TrustedOverlay.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+
+/**
+  * Trusted overlay state prevents layers from being considered as obscuring for
+  * input occlusion detection purposes.
+  *
+  * @hide
+  */
+@Backing(type="int")
+enum TrustedOverlay {
+    /**
+      * The default, layer will inherit the state from its parents. If the parent state is also
+      * unset, the layer will be considered as untrusted.
+      */
+    UNSET,
+
+    /**
+      * Treats this layer and all its children as an untrusted overlay. This will override any
+      * state set by its parent layers.
+      */
+    DISABLED,
+
+    /**
+      * Treats this layer and all its children as a trusted overlay unless the child layer
+      * explicitly disables its trusted state.
+      */
+    ENABLED
+}
diff --git a/libs/gui/android/gui/WindowInfo.aidl b/libs/gui/android/gui/WindowInfo.aidl
index 2c85d15..b9d5ccf 100644
--- a/libs/gui/android/gui/WindowInfo.aidl
+++ b/libs/gui/android/gui/WindowInfo.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable WindowInfo cpp_header "gui/WindowInfo.h";
+parcelable WindowInfo cpp_header "gui/WindowInfo.h" rust_type "gui_aidl_types_rs::WindowInfo";
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
index 0c6109d..5c23e08 100644
--- a/libs/gui/android/gui/WindowInfosUpdate.aidl
+++ b/libs/gui/android/gui/WindowInfosUpdate.aidl
@@ -19,4 +19,4 @@
 import android.gui.DisplayInfo;
 import android.gui.WindowInfo;
 
-parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h";
+parcelable WindowInfosUpdate cpp_header "gui/WindowInfosUpdate.h" rust_type "gui_aidl_types_rs::WindowInfosUpdate";
diff --git a/libs/gui/include/gui/AdditionalOptions.h b/libs/gui/include/gui/AdditionalOptions.h
new file mode 100644
index 0000000..87cb913
--- /dev/null
+++ b/libs/gui/include/gui/AdditionalOptions.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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 <string>
+
+namespace android::gui {
+// Additional options to pass to AHardwareBuffer_allocateWithOptions.
+// See also allocator-v2's BufferDescriptorInfo.aidl
+struct AdditionalOptions {
+    std::string name;
+    int64_t value;
+
+    bool operator==(const AdditionalOptions& other) const {
+        return value == other.value && name == other.name;
+    }
+};
+} // namespace android::gui
diff --git a/libs/gui/include/gui/AidlStatusUtil.h b/libs/gui/include/gui/AidlStatusUtil.h
deleted file mode 100644
index 55be27b..0000000
--- a/libs/gui/include/gui/AidlStatusUtil.h
+++ /dev/null
@@ -1,114 +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 <binder/Status.h>
-
-// Extracted from frameworks/av/media/libaudioclient/include/media/AidlConversionUtil.h
-namespace android::gui::aidl_utils {
-
-/**
- * Return the equivalent Android status_t from a binder exception code.
- *
- * Generally one should use statusTFromBinderStatus() instead.
- *
- * Exception codes can be generated from a remote Java service exception, translate
- * them for use on the Native side.
- *
- * Note: for EX_TRANSACTION_FAILED and EX_SERVICE_SPECIFIC a more detailed error code
- * can be found from transactionError() or serviceSpecificErrorCode().
- */
-static inline status_t statusTFromExceptionCode(int32_t exceptionCode) {
-    using namespace ::android::binder;
-    switch (exceptionCode) {
-        case Status::EX_NONE:
-            return OK;
-        case Status::EX_SECURITY: // Java SecurityException, rethrows locally in Java
-            return PERMISSION_DENIED;
-        case Status::EX_BAD_PARCELABLE:   // Java BadParcelableException, rethrows in Java
-        case Status::EX_ILLEGAL_ARGUMENT: // Java IllegalArgumentException, rethrows in Java
-        case Status::EX_NULL_POINTER:     // Java NullPointerException, rethrows in Java
-            return BAD_VALUE;
-        case Status::EX_ILLEGAL_STATE:         // Java IllegalStateException, rethrows in Java
-        case Status::EX_UNSUPPORTED_OPERATION: // Java UnsupportedOperationException, rethrows
-            return INVALID_OPERATION;
-        case Status::EX_HAS_REPLY_HEADER: // Native strictmode violation
-        case Status::EX_PARCELABLE: // Java bootclass loader (not standard exception), rethrows
-        case Status::EX_NETWORK_MAIN_THREAD: // Java NetworkOnMainThreadException, rethrows
-        case Status::EX_TRANSACTION_FAILED:  // Native - see error code
-        case Status::EX_SERVICE_SPECIFIC:    // Java ServiceSpecificException,
-                                             // rethrows in Java with integer error code
-            return UNKNOWN_ERROR;
-    }
-    return UNKNOWN_ERROR;
-}
-
-/**
- * Return the equivalent Android status_t from a binder status.
- *
- * Used to handle errors from a AIDL method declaration
- *
- * [oneway] void method(type0 param0, ...)
- *
- * or the following (where return_type is not a status_t)
- *
- * return_type method(type0 param0, ...)
- */
-static inline status_t statusTFromBinderStatus(const ::android::binder::Status &status) {
-    return status.isOk() ? OK // check OK,
-        : status.serviceSpecificErrorCode() // service-side error, not standard Java exception
-                                            // (fromServiceSpecificError)
-        ?: status.transactionError() // a native binder transaction error (fromStatusT)
-        ?: statusTFromExceptionCode(status.exceptionCode()); // a service-side error with a
-                                                    // standard Java exception (fromExceptionCode)
-}
-
-/**
- * Return a binder::Status from native service status.
- *
- * This is used for methods not returning an explicit status_t,
- * where Java callers expect an exception, not an integer return value.
- */
-static inline ::android::binder::Status binderStatusFromStatusT(
-        status_t status, const char *optionalMessage = nullptr) {
-    const char *const emptyIfNull = optionalMessage == nullptr ? "" : optionalMessage;
-    // From binder::Status instructions:
-    //  Prefer a generic exception code when possible, then a service specific
-    //  code, and finally a status_t for low level failures or legacy support.
-    //  Exception codes and service specific errors map to nicer exceptions for
-    //  Java clients.
-
-    using namespace ::android::binder;
-    switch (status) {
-        case OK:
-            return Status::ok();
-        case PERMISSION_DENIED: // throw SecurityException on Java side
-            return Status::fromExceptionCode(Status::EX_SECURITY, emptyIfNull);
-        case BAD_VALUE: // throw IllegalArgumentException on Java side
-            return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, emptyIfNull);
-        case INVALID_OPERATION: // throw IllegalStateException on Java side
-            return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, emptyIfNull);
-    }
-
-    // A service specific error will not show on status.transactionError() so
-    // be sure to use statusTFromBinderStatus() for reliable error handling.
-
-    // throw a ServiceSpecificException.
-    return Status::fromServiceSpecificError(status, emptyIfNull);
-}
-
-} // namespace android::gui::aidl_utils
diff --git a/libs/gui/include/gui/AidlUtil.h b/libs/gui/include/gui/AidlUtil.h
new file mode 100644
index 0000000..a3ecd84
--- /dev/null
+++ b/libs/gui/include/gui/AidlUtil.h
@@ -0,0 +1,138 @@
+/*
+ * 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 <android/gui/ARect.h>
+#include <binder/Status.h>
+#include <ui/Rect.h>
+
+// Originally extracted from frameworks/av/media/libaudioclient/include/media/AidlConversionUtil.h
+namespace android::gui::aidl_utils {
+
+/**
+ * Return the equivalent Android status_t from a binder exception code.
+ *
+ * Generally one should use statusTFromBinderStatus() instead.
+ *
+ * Exception codes can be generated from a remote Java service exception, translate
+ * them for use on the Native side.
+ *
+ * Note: for EX_TRANSACTION_FAILED and EX_SERVICE_SPECIFIC a more detailed error code
+ * can be found from transactionError() or serviceSpecificErrorCode().
+ */
+static inline status_t statusTFromExceptionCode(int32_t exceptionCode) {
+    using namespace ::android::binder;
+    switch (exceptionCode) {
+        case Status::EX_NONE:
+            return OK;
+        case Status::EX_SECURITY: // Java SecurityException, rethrows locally in Java
+            return PERMISSION_DENIED;
+        case Status::EX_BAD_PARCELABLE:   // Java BadParcelableException, rethrows in Java
+        case Status::EX_ILLEGAL_ARGUMENT: // Java IllegalArgumentException, rethrows in Java
+        case Status::EX_NULL_POINTER:     // Java NullPointerException, rethrows in Java
+            return BAD_VALUE;
+        case Status::EX_ILLEGAL_STATE:         // Java IllegalStateException, rethrows in Java
+        case Status::EX_UNSUPPORTED_OPERATION: // Java UnsupportedOperationException, rethrows
+            return INVALID_OPERATION;
+        case Status::EX_HAS_REPLY_HEADER: // Native strictmode violation
+        case Status::EX_PARCELABLE: // Java bootclass loader (not standard exception), rethrows
+        case Status::EX_NETWORK_MAIN_THREAD: // Java NetworkOnMainThreadException, rethrows
+        case Status::EX_TRANSACTION_FAILED:  // Native - see error code
+        case Status::EX_SERVICE_SPECIFIC:    // Java ServiceSpecificException,
+                                             // rethrows in Java with integer error code
+            return UNKNOWN_ERROR;
+    }
+    return UNKNOWN_ERROR;
+}
+
+/**
+ * Return the equivalent Android status_t from a binder status.
+ *
+ * Used to handle errors from a AIDL method declaration
+ *
+ * [oneway] void method(type0 param0, ...)
+ *
+ * or the following (where return_type is not a status_t)
+ *
+ * return_type method(type0 param0, ...)
+ */
+static inline status_t statusTFromBinderStatus(const ::android::binder::Status& status) {
+    return status.isOk() ? OK // check OK,
+        : status.serviceSpecificErrorCode() // service-side error, not standard Java exception
+                                            // (fromServiceSpecificError)
+        ?: status.transactionError() // a native binder transaction error (fromStatusT)
+        ?: statusTFromExceptionCode(status.exceptionCode()); // a service-side error with a
+                                                    // standard Java exception (fromExceptionCode)
+}
+
+/**
+ * Return a binder::Status from native service status.
+ *
+ * This is used for methods not returning an explicit status_t,
+ * where Java callers expect an exception, not an integer return value.
+ */
+static inline ::android::binder::Status binderStatusFromStatusT(
+        status_t status, const char* optionalMessage = nullptr) {
+    const char* const emptyIfNull = optionalMessage == nullptr ? "" : optionalMessage;
+    // From binder::Status instructions:
+    //  Prefer a generic exception code when possible, then a service specific
+    //  code, and finally a status_t for low level failures or legacy support.
+    //  Exception codes and service specific errors map to nicer exceptions for
+    //  Java clients.
+
+    using namespace ::android::binder;
+    switch (status) {
+        case OK:
+            return Status::ok();
+        case PERMISSION_DENIED: // throw SecurityException on Java side
+            return Status::fromExceptionCode(Status::EX_SECURITY, emptyIfNull);
+        case BAD_VALUE: // throw IllegalArgumentException on Java side
+            return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, emptyIfNull);
+        case INVALID_OPERATION: // throw IllegalStateException on Java side
+            return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, emptyIfNull);
+    }
+
+    // A service specific error will not show on status.transactionError() so
+    // be sure to use statusTFromBinderStatus() for reliable error handling.
+
+    // throw a ServiceSpecificException.
+    return Status::fromServiceSpecificError(status, emptyIfNull);
+}
+
+static inline Rect fromARect(ARect rect) {
+    return Rect(rect.left, rect.top, rect.right, rect.bottom);
+}
+
+static inline ARect toARect(Rect rect) {
+    ARect aRect;
+
+    aRect.left = rect.left;
+    aRect.top = rect.top;
+    aRect.right = rect.right;
+    aRect.bottom = rect.bottom;
+    return aRect;
+}
+
+static inline ARect toARect(int32_t left, int32_t top, int32_t right, int32_t bottom) {
+    return toARect(Rect(left, top, right, bottom));
+}
+
+static inline ARect toARect(int32_t width, int32_t height) {
+    return toARect(Rect(width, height));
+}
+
+} // namespace android::gui::aidl_utils
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 0e1a505..8592cff 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,9 +17,10 @@
 #ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 #define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferItemConsumer.h>
-
+#include <gui/IGraphicBufferConsumer.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/SurfaceComposerClient.h>
 
@@ -28,7 +29,6 @@
 #include <utils/RefBase.h>
 
 #include <system/window.h>
-#include <thread>
 #include <queue>
 
 #include <com_android_graphics_libgui_flags.h>
@@ -40,12 +40,20 @@
 
 class BLASTBufferItemConsumer : public BufferItemConsumer {
 public:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    BLASTBufferItemConsumer(const sp<IGraphicBufferProducer>& producer,
+                            const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
+                            int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
+          : BufferItemConsumer(producer, consumer, consumerUsage, bufferCount, controlledByApp),
+#else
     BLASTBufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
                             int bufferCount, bool controlledByApp, wp<BLASTBufferQueue> bbq)
           : BufferItemConsumer(consumer, consumerUsage, bufferCount, controlledByApp),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
             mBLASTBufferQueue(std::move(bbq)),
             mCurrentlyConnected(false),
-            mPreviouslyConnected(false) {}
+            mPreviouslyConnected(false) {
+    }
 
     void onDisconnect() override EXCLUDES(mMutex);
     void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
@@ -95,15 +103,21 @@
     void onFrameDequeued(const uint64_t) override;
     void onFrameCancelled(const uint64_t) override;
 
+    TransactionCompletedCallbackTakesContext makeTransactionCommittedCallbackThunk();
     void transactionCommittedCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
                                       const std::vector<SurfaceControlStats>& stats);
+
+    TransactionCompletedCallbackTakesContext makeTransactionCallbackThunk();
     virtual void transactionCallback(nsecs_t latchTime, const sp<Fence>& presentFence,
                                      const std::vector<SurfaceControlStats>& stats);
+
+    ReleaseBufferCallback makeReleaseBufferCallbackThunk();
     void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                std::optional<uint32_t> currentMaxAcquiredBufferCount);
     void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
                                      std::optional<uint32_t> currentMaxAcquiredBufferCount,
                                      bool fakeRelease) REQUIRES(mMutex);
+
     bool syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
                              bool acquireSingleBuffer = true);
     void stopContinuousSyncTransaction();
@@ -128,9 +142,11 @@
      * indicates the reason for the hang.
      */
     void setTransactionHangCallback(std::function<void(const std::string&)> callback);
-
+    void setApplyToken(sp<IBinder>);
     virtual ~BLASTBufferQueue();
 
+    void onFirstRef() override;
+
 private:
     friend class BLASTBufferQueueHelper;
     friend class BBQBufferQueueProducer;
@@ -170,8 +186,7 @@
 
     // BufferQueue internally allows 1 more than
     // the max to be acquired
-    int32_t mMaxAcquiredBuffers = 1;
-
+    int32_t mMaxAcquiredBuffers GUARDED_BY(mMutex) = 1;
     int32_t mNumFrameAvailable GUARDED_BY(mMutex) = 0;
     int32_t mNumAcquired GUARDED_BY(mMutex) = 0;
 
@@ -256,7 +271,7 @@
 
     // Queues up transactions using this token in SurfaceFlinger. This prevents queued up
     // transactions from other parts of the client from blocking this transaction.
-    const sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
+    sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
 
     // Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or
     // we will deadlock.
@@ -300,6 +315,51 @@
     std::function<void(const std::string&)> mTransactionHangCallback;
 
     std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
+    class BufferReleaseReader {
+    public:
+        BufferReleaseReader() = default;
+        BufferReleaseReader(std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint>);
+        BufferReleaseReader& operator=(BufferReleaseReader&&);
+
+        // Block until we can read a buffer release message.
+        //
+        // Returns:
+        // * OK if a ReleaseCallbackId and Fence were successfully read.
+        // * WOULD_BLOCK if the blocking read was interrupted by interruptBlockingRead.
+        // * UNKNOWN_ERROR if something went wrong.
+        status_t readBlocking(ReleaseCallbackId& outId, sp<Fence>& outReleaseFence,
+                              uint32_t& outMaxAcquiredBufferCount);
+
+        // Signals the reader's eventfd to wake up any threads waiting on readBlocking.
+        void interruptBlockingRead();
+
+    private:
+        std::mutex mMutex;
+        std::unique_ptr<gui::BufferReleaseChannel::ConsumerEndpoint> mEndpoint GUARDED_BY(mMutex);
+        android::base::unique_fd mEpollFd;
+        android::base::unique_fd mEventFd;
+    };
+
+    // BufferReleaseChannel is used to communicate buffer releases from SurfaceFlinger to
+    // the client. See BBQBufferQueueProducer::dequeueBuffer for details.
+    std::shared_ptr<BufferReleaseReader> mBufferReleaseReader;
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseProducer;
+
+    class BufferReleaseThread {
+    public:
+        BufferReleaseThread() = default;
+        ~BufferReleaseThread();
+        void start(const sp<BLASTBufferQueue>&);
+
+    private:
+        std::shared_ptr<std::atomic_bool> mRunning;
+        std::shared_ptr<BufferReleaseReader> mReader;
+    };
+
+    BufferReleaseThread mBufferReleaseThread;
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/BufferItemConsumer.h b/libs/gui/include/gui/BufferItemConsumer.h
index a905610..6810eda 100644
--- a/libs/gui/include/gui/BufferItemConsumer.h
+++ b/libs/gui/include/gui/BufferItemConsumer.h
@@ -17,13 +17,15 @@
 #ifndef ANDROID_GUI_BUFFERITEMCONSUMER_H
 #define ANDROID_GUI_BUFFERITEMCONSUMER_H
 
-#include <gui/ConsumerBase.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferQueue.h>
+#include <gui/ConsumerBase.h>
 
 #define ANDROID_GRAPHICS_BUFFERITEMCONSUMER_JNI_ID "mBufferItemConsumer"
 
 namespace android {
 
+class GraphicBuffer;
 class String8;
 
 /**
@@ -51,9 +53,17 @@
     // access at the same time.
     // controlledByApp tells whether this consumer is controlled by the
     // application.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    BufferItemConsumer(uint64_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
+                       bool controlledByApp = false, bool isConsumerSurfaceFlinger = false);
+    BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
+                       int bufferCount = DEFAULT_MAX_BUFFERS, bool controlledByApp = false)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+#else
     BufferItemConsumer(const sp<IGraphicBufferConsumer>& consumer,
             uint64_t consumerUsage, int bufferCount = DEFAULT_MAX_BUFFERS,
             bool controlledByApp = false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     ~BufferItemConsumer() override;
 
@@ -85,7 +95,25 @@
     status_t releaseBuffer(const BufferItem &item,
             const sp<Fence>& releaseFence = Fence::NO_FENCE);
 
-   private:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    status_t releaseBuffer(const sp<GraphicBuffer>& buffer,
+                           const sp<Fence>& releaseFence = Fence::NO_FENCE);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+protected:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    // This should only be used by BLASTBufferQueue:
+    BufferItemConsumer(const sp<IGraphicBufferProducer>& producer,
+                       const sp<IGraphicBufferConsumer>& consumer, uint64_t consumerUsage,
+                       int bufferCount = DEFAULT_MAX_BUFFERS, bool controlledByApp = false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
+private:
+    void initialize(uint64_t consumerUsage, int bufferCount);
+
+    status_t releaseBufferSlotLocked(int slotIndex, const sp<GraphicBuffer>& buffer,
+                                     const sp<Fence>& releaseFence);
+
     void freeBufferLocked(int slotIndex) override;
 
     // mBufferFreedListener is the listener object that will be called when
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index c16e370..d5dd7c8 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -17,6 +17,9 @@
 #ifndef ANDROID_GUI_BUFFERQUEUECORE_H
 #define ANDROID_GUI_BUFFERQUEUECORE_H
 
+#include <com_android_graphics_libgui_flags.h>
+
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/BufferSlot.h>
@@ -361,6 +364,14 @@
     // This allows the consumer to acquire an additional buffer if that buffer is not droppable and
     // will eventually be released or acquired by the consumer.
     bool mAllowExtraAcquire = false;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // Additional options to pass when allocating GraphicBuffers.
+    // GenerationID changes when the options change, indicating reallocation is required
+    uint32_t mAdditionalOptionsGenerationId = 0;
+    std::vector<gui::AdditionalOptions> mAdditionalOptions;
+#endif
+
 }; // class BufferQueueCore
 
 } // namespace android
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index de47483..37a9607 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 #define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
 
+#include <gui/AdditionalOptions.h>
 #include <gui/BufferQueueDefs.h>
 
 #include <gui/IGraphicBufferProducer.h>
@@ -208,6 +209,10 @@
                           int8_t changeFrameRateStrategy) override;
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options) override;
+#endif
+
 protected:
     // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
     // total maximum buffer count for the buffer queue (dequeued AND acquired)
diff --git a/libs/gui/include/gui/BufferReleaseChannel.h b/libs/gui/include/gui/BufferReleaseChannel.h
new file mode 100644
index 0000000..51fe0b6
--- /dev/null
+++ b/libs/gui/include/gui/BufferReleaseChannel.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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 <string>
+#include <vector>
+
+#include <android-base/unique_fd.h>
+
+#include <binder/Parcelable.h>
+#include <gui/ITransactionCompletedListener.h>
+#include <ui/Fence.h>
+#include <utils/Errors.h>
+
+namespace android::gui {
+
+/**
+ * IPC wrapper to pass release fences from SurfaceFlinger to apps via a local unix domain socket.
+ */
+class BufferReleaseChannel {
+private:
+    class Endpoint {
+    public:
+        Endpoint(std::string name, android::base::unique_fd fd)
+              : mName(std::move(name)), mFd(std::move(fd)) {}
+        Endpoint() {}
+
+        Endpoint(Endpoint&&) noexcept = default;
+        Endpoint& operator=(Endpoint&&) noexcept = default;
+
+        Endpoint(const Endpoint&) = delete;
+        void operator=(const Endpoint&) = delete;
+
+        const android::base::unique_fd& getFd() const { return mFd; }
+
+    protected:
+        std::string mName;
+        android::base::unique_fd mFd;
+    };
+
+public:
+    class ConsumerEndpoint : public Endpoint {
+    public:
+        ConsumerEndpoint(std::string name, android::base::unique_fd fd)
+              : Endpoint(std::move(name), std::move(fd)) {}
+
+        /**
+         * Reads a release fence from the BufferReleaseChannel.
+         *
+         * Returns OK on success.
+         * Returns WOULD_BLOCK if there is no fence present.
+         * Other errors probably indicate that the channel is broken.
+         */
+        status_t readReleaseFence(ReleaseCallbackId& outReleaseCallbackId,
+                                  sp<Fence>& outReleaseFence, uint32_t& maxAcquiredBufferCount);
+
+    private:
+        std::vector<uint8_t> mFlattenedBuffer;
+    };
+
+    class ProducerEndpoint : public Endpoint, public Parcelable {
+    public:
+        ProducerEndpoint(std::string name, android::base::unique_fd fd)
+              : Endpoint(std::move(name), std::move(fd)) {}
+        ProducerEndpoint() {}
+
+        status_t readFromParcel(const android::Parcel* parcel) override;
+        status_t writeToParcel(android::Parcel* parcel) const override;
+
+        status_t writeReleaseFence(const ReleaseCallbackId&, const sp<Fence>& releaseFence,
+                                   uint32_t maxAcquiredBufferCount);
+
+    private:
+        std::vector<uint8_t> mFlattenedBuffer;
+    };
+
+    /**
+     * Create two endpoints that make up the BufferReleaseChannel.
+     *
+     * Return OK on success.
+     */
+    static status_t open(const std::string name, std::unique_ptr<ConsumerEndpoint>& outConsumer,
+                         std::shared_ptr<ProducerEndpoint>& outProducer);
+
+    struct Message : public Flattenable<Message> {
+        ReleaseCallbackId releaseCallbackId;
+        sp<Fence> releaseFence = Fence::NO_FENCE;
+        uint32_t maxAcquiredBufferCount;
+
+        Message() = default;
+        Message(ReleaseCallbackId releaseCallbackId, sp<Fence> releaseFence,
+                uint32_t maxAcquiredBufferCount)
+              : releaseCallbackId{releaseCallbackId},
+                releaseFence{std::move(releaseFence)},
+                maxAcquiredBufferCount{maxAcquiredBufferCount} {}
+
+        // Flattenable protocol
+        size_t getFlattenedSize() const;
+
+        size_t getFdCount() const { return releaseFence->getFdCount(); }
+
+        status_t flatten(void*& buffer, size_t& size, int*& fds, size_t& count) const;
+
+        status_t unflatten(void const*& buffer, size_t& size, int const*& fds, size_t& count);
+
+    private:
+        size_t getPodSize() const;
+    };
+};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/BufferSlot.h b/libs/gui/include/gui/BufferSlot.h
index 57704b1..5b32710 100644
--- a/libs/gui/include/gui/BufferSlot.h
+++ b/libs/gui/include/gui/BufferSlot.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_GUI_BUFFERSLOT_H
 #define ANDROID_GUI_BUFFERSLOT_H
 
+#include <com_android_graphics_libgui_flags.h>
+
 #include <ui/Fence.h>
 #include <ui/GraphicBuffer.h>
 
@@ -230,6 +232,11 @@
     // producer. If so, it needs to set the BUFFER_NEEDS_REALLOCATION flag when
     // dequeued to prevent the producer from using a stale cached buffer.
     bool mNeedsReallocation;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    // The generation id of the additional options that mGraphicBuffer was allocated with
+    uint32_t mAdditionalOptionsGenerationId = 0;
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
index 55a7aa7..2e5aa4a 100644
--- a/libs/gui/include/gui/Choreographer.h
+++ b/libs/gui/include/gui/Choreographer.h
@@ -28,12 +28,18 @@
 namespace android {
 using gui::VsyncEventData;
 
+enum CallbackType : int8_t {
+    CALLBACK_INPUT,
+    CALLBACK_ANIMATION,
+};
+
 struct FrameCallback {
     AChoreographer_frameCallback callback;
     AChoreographer_frameCallback64 callback64;
     AChoreographer_vsyncCallback vsyncCallback;
     void* data;
     nsecs_t dueTime;
+    CallbackType callbackType;
 
     inline bool operator<(const FrameCallback& rhs) const {
         // Note that this is intentionally flipped because we want callbacks due sooner to be at
@@ -78,7 +84,7 @@
     void postFrameCallbackDelayed(AChoreographer_frameCallback cb,
                                   AChoreographer_frameCallback64 cb64,
                                   AChoreographer_vsyncCallback vsyncCallback, void* data,
-                                  nsecs_t delay);
+                                  nsecs_t delay, CallbackType callbackType);
     void registerRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data)
             EXCLUDES(gChoreographers.lock);
     void unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb, void* data);
@@ -103,12 +109,15 @@
     virtual ~Choreographer() override EXCLUDES(gChoreographers.lock);
     int64_t getFrameInterval() const;
     bool inCallback() const;
+    const sp<Looper> getLooper();
 
 private:
     Choreographer(const Choreographer&) = delete;
 
     void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count,
                        VsyncEventData vsyncEventData) override;
+    void dispatchCallbacks(const std::vector<FrameCallback>&, VsyncEventData vsyncEventData,
+                           nsecs_t timestamp);
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
diff --git a/libs/gui/include/gui/ConsumerBase.h b/libs/gui/include/gui/ConsumerBase.h
index 8ff0cd0..e976aa4 100644
--- a/libs/gui/include/gui/ConsumerBase.h
+++ b/libs/gui/include/gui/ConsumerBase.h
@@ -17,18 +17,17 @@
 #ifndef ANDROID_GUI_CONSUMERBASE_H
 #define ANDROID_GUI_CONSUMERBASE_H
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/IConsumerListener.h>
 #include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <gui/OccupancyTracker.h>
-
 #include <ui/PixelFormat.h>
-
 #include <utils/String8.h>
 #include <utils/Vector.h>
 #include <utils/threads.h>
 
-
 namespace android {
 // ----------------------------------------------------------------------------
 
@@ -76,12 +75,28 @@
     void dumpState(String8& result) const;
     void dumpState(String8& result, const char* prefix) const;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    // Returns a Surface that can be used as the producer for this consumer.
+    sp<Surface> getSurface() const;
+
+    // DEPRECATED, DO NOT USE. Returns the underlying IGraphicBufferConsumer
+    // that backs this ConsumerBase.
+    sp<IGraphicBufferConsumer> getIGraphicBufferConsumer() const
+            __attribute((deprecated("DO NOT USE: Temporary hack for refactoring")));
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
     // setFrameAvailableListener sets the listener object that will be notified
     // when a new frame becomes available.
     void setFrameAvailableListener(const wp<FrameAvailableListener>& listener);
 
     // See IGraphicBufferConsumer::detachBuffer
-    status_t detachBuffer(int slot);
+    status_t detachBuffer(int slot) __attribute((
+            deprecated("Please use the GraphicBuffer variant--slots are deprecated.")));
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    // See IGraphicBufferConsumer::detachBuffer
+    status_t detachBuffer(const sp<GraphicBuffer>& buffer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
     // See IGraphicBufferConsumer::setDefaultBufferSize
     status_t setDefaultBufferSize(uint32_t width, uint32_t height);
@@ -98,9 +113,18 @@
     // See IGraphicBufferConsumer::setTransformHint
     status_t setTransformHint(uint32_t hint);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    // See IGraphicBufferConsumer::setMaxBufferCount
+    status_t setMaxBufferCount(int bufferCount);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
     // See IGraphicBufferConsumer::setMaxAcquiredBufferCount
     status_t setMaxAcquiredBufferCount(int maxAcquiredBuffers);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    status_t setConsumerIsProtected(bool isProtected);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
     // See IGraphicBufferConsumer::getSidebandStream
     sp<NativeHandle> getSidebandStream() const;
 
@@ -115,12 +139,24 @@
     ConsumerBase(const ConsumerBase&);
     void operator=(const ConsumerBase&);
 
+    void initialize(bool controlledByApp);
+
 protected:
     // ConsumerBase constructs a new ConsumerBase object to consume image
     // buffers from the given IGraphicBufferConsumer.
     // The controlledByApp flag indicates that this consumer is under the application's
     // control.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    explicit ConsumerBase(bool controlledByApp = false, bool consumerIsSurfaceFlinger = false);
+    explicit ConsumerBase(const sp<IGraphicBufferProducer>& producer,
+                          const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp = false);
+
+    explicit ConsumerBase(const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp = false)
+            __attribute((deprecated("ConsumerBase should own its own producer, and constructing it "
+                                    "without one is fragile! This method is going away soon.")));
+#else
     explicit ConsumerBase(const sp<IGraphicBufferConsumer>& consumer, bool controlledByApp = false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     // onLastStrongRef gets called by RefBase just before the dtor of the most
     // derived class.  It is used to clean up the buffers so that ConsumerBase
@@ -150,6 +186,10 @@
     virtual void onBuffersReleased() override;
     virtual void onSidebandStreamChanged() override;
 
+    virtual int getSlotForBufferLocked(const sp<GraphicBuffer>& buffer);
+
+    virtual status_t detachBufferLocked(int slotIndex);
+
     // freeBufferLocked frees up the given buffer slot.  If the slot has been
     // initialized this will release the reference to the GraphicBuffer in that
     // slot.  Otherwise it has no effect.
@@ -264,10 +304,16 @@
     Mutex mFrameAvailableMutex;
     wp<FrameAvailableListener> mFrameAvailableListener;
 
-    // The ConsumerBase has-a BufferQueue and is responsible for creating this object
-    // if none is supplied
+    // The ConsumerBase has-a BufferQueue and is responsible for creating these
+    // objects if not supplied.
     sp<IGraphicBufferConsumer> mConsumer;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    // This Surface wraps the IGraphicBufferConsumer created for this
+    // ConsumerBase.
+    sp<Surface> mSurface;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
     // The final release fence of the most recent buffer released by
     // releaseBufferLocked.
     sp<Fence> mPrevFinalReleaseFence;
diff --git a/libs/gui/include/gui/CpuConsumer.h b/libs/gui/include/gui/CpuConsumer.h
index 806fbe8..2bba61b 100644
--- a/libs/gui/include/gui/CpuConsumer.h
+++ b/libs/gui/include/gui/CpuConsumer.h
@@ -19,8 +19,9 @@
 
 #include <system/window.h>
 
-#include <gui/ConsumerBase.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferQueue.h>
+#include <gui/ConsumerBase.h>
 
 #include <utils/Vector.h>
 
@@ -91,8 +92,17 @@
 
     // Create a new CPU consumer. The maxLockedBuffers parameter specifies
     // how many buffers can be locked for user access at the same time.
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    CpuConsumer(size_t maxLockedBuffers, bool controlledByApp = false,
+                bool isConsumerSurfaceFlinger = false);
+
+    CpuConsumer(const sp<IGraphicBufferConsumer>& bq, size_t maxLockedBuffers,
+                bool controlledByApp = false)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+#else
     CpuConsumer(const sp<IGraphicBufferConsumer>& bq,
             size_t maxLockedBuffers, bool controlledByApp = false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     // Gets the next graphics buffer from the producer and locks it for CPU use,
     // filling out the passed-in locked buffer structure with the native pointer
diff --git a/libs/gui/include/gui/DisplayCaptureArgs.h b/libs/gui/include/gui/DisplayCaptureArgs.h
deleted file mode 100644
index e29ce41..0000000
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ /dev/null
@@ -1,84 +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 <stdint.h>
-#include <sys/types.h>
-
-#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 {
-
-struct CaptureArgs : public Parcelable {
-    const static int32_t UNSET_UID = -1;
-    virtual ~CaptureArgs() = default;
-
-    ui::PixelFormat pixelFormat{ui::PixelFormat::RGBA_8888};
-    Rect sourceCrop;
-    float frameScaleX{1};
-    float frameScaleY{1};
-    bool captureSecureLayers{false};
-    int32_t uid{UNSET_UID};
-    // Force capture to be in a color space. If the value is ui::Dataspace::UNKNOWN, the captured
-    // result will be in a colorspace appropriate for capturing the display contents
-    // The display may use non-RGB dataspace (ex. displayP3) that could cause pixel data could be
-    // different from SRGB (byte per color), and failed when checking colors in tests.
-    // NOTE: In normal cases, we want the screen to be captured in display's colorspace.
-    ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
-
-    // The receiver of the capture can handle protected buffer. A protected buffer has
-    // GRALLOC_USAGE_PROTECTED usage bit and must not be accessed unprotected behaviour.
-    // Any read/write access from unprotected context will result in undefined behaviour.
-    // Protected contents are typically DRM contents. This has no direct implication to the
-    // secure property of the surface, which is specified by the application explicitly to avoid
-    // the contents being accessed/captured by screenshot or unsecure display.
-    bool allowProtected = false;
-
-    bool grayscale = false;
-
-    std::unordered_set<sp<IBinder>, SpHash<IBinder>> excludeHandles;
-
-    // Hint that the caller will use the screenshot animation as part of a transition animation.
-    // The canonical example would be screen rotation - in such a case any color shift in the
-    // screenshot is a detractor so composition in the display's colorspace is required.
-    // Otherwise, the system may choose a colorspace that is more appropriate for use-cases
-    // such as file encoding or for blending HDR content into an ap's UI, where the display's
-    // exact colorspace is not an appropriate intermediate result.
-    // Note that if the caller is requesting a specific dataspace, this hint does nothing.
-    bool hintForSeamlessTransition = false;
-
-    virtual status_t writeToParcel(Parcel* output) const;
-    virtual status_t readFromParcel(const Parcel* input);
-};
-
-struct DisplayCaptureArgs : CaptureArgs {
-    sp<IBinder> displayToken;
-    uint32_t width{0};
-    uint32_t height{0};
-
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-};
-
-}; // namespace android::gui
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index 8c1103b..4dbf9e1 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -119,6 +119,7 @@
             HdcpLevelsChange hdcpLevelsChange;
         };
     };
+    static_assert(sizeof(Event) == 216);
 
 public:
     /*
diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h
index 42b62c7..7094658 100644
--- a/libs/gui/include/gui/DisplayInfo.h
+++ b/libs/gui/include/gui/DisplayInfo.h
@@ -18,7 +18,7 @@
 
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
-#include <gui/constants.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 
 namespace android::gui {
@@ -29,7 +29,7 @@
  * This should only be used by InputFlinger to support raw coordinates in logical display space.
  */
 struct DisplayInfo : public Parcelable {
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
 
     // Logical display dimensions.
     int32_t logicalWidth = 0;
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
new file mode 100644
index 0000000..735375a
--- /dev/null
+++ b/libs/gui/include/gui/Flags.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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 <com_android_graphics_libgui_flags.h>
+
+#define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES                  \
+    (COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) &&  \
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
\ No newline at end of file
diff --git a/libs/gui/include/gui/FrameTimestamps.h b/libs/gui/include/gui/FrameTimestamps.h
index 3d1be4d..462081b 100644
--- a/libs/gui/include/gui/FrameTimestamps.h
+++ b/libs/gui/include/gui/FrameTimestamps.h
@@ -116,7 +116,7 @@
     // Public for testing.
     static nsecs_t snapToNextTick(
             nsecs_t timestamp, nsecs_t tickPhase, nsecs_t tickInterval);
-    nsecs_t getReportedCompositeDeadline() const { return mCompositorTiming.deadline; };
+    nsecs_t getReportedCompositeDeadline() const { return mCompositorTiming.deadline; }
 
     nsecs_t getNextCompositeDeadline(const nsecs_t now) const;
     nsecs_t getCompositeInterval() const { return mCompositorTiming.interval; }
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index ba268ab..bfe3eb3 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -20,6 +20,7 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/ConsumerBase.h>
 
@@ -82,12 +83,25 @@
     // If the constructor without the tex parameter is used, the GLConsumer is
     // created in a detached state, and attachToContext must be called before
     // calls to updateTexImage.
-    GLConsumer(const sp<IGraphicBufferConsumer>& bq,
-            uint32_t tex, uint32_t texureTarget, bool useFenceSync,
-            bool isControlledByApp);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    GLConsumer(uint32_t tex, uint32_t textureTarget, bool useFenceSync, bool isControlledByApp);
 
-    GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t texureTarget,
-            bool useFenceSync, bool isControlledByApp);
+    GLConsumer(uint32_t textureTarget, bool useFenceSync, bool isControlledByApp);
+
+    GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t textureTarget,
+               bool useFenceSync, bool isControlledByApp)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+
+    GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t textureTarget, bool useFenceSync,
+               bool isControlledByApp)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+#else
+    GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t textureTarget,
+               bool useFenceSync, bool isControlledByApp);
+
+    GLConsumer(const sp<IGraphicBufferConsumer>& bq, uint32_t textureTarget, bool useFenceSync,
+               bool isControlledByApp);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     // updateTexImage acquires the most recently queued buffer, and sets the
     // image contents of the target texture to it.
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 7639e70..197e792 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -31,6 +31,7 @@
 #include <ui/Rect.h>
 #include <ui/Region.h>
 
+#include <gui/AdditionalOptions.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/HdrMetadata.h>
 
@@ -402,7 +403,7 @@
         uint64_t nextFrameNumber{0};
         FrameEventHistoryDelta frameTimestamps;
         bool bufferReplaced{false};
-        int maxBufferCount{0};
+        int maxBufferCount{BufferQueueDefs::NUM_BUFFER_SLOTS};
         status_t result{NO_ERROR};
     };
 
@@ -684,6 +685,10 @@
                                   int8_t changeFrameRateStrategy);
 #endif
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    virtual status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
     struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
         RequestBufferOutput() = default;
 
@@ -862,6 +867,6 @@
 #endif
 
 // ----------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_GUI_IGRAPHICBUFFERPRODUCER_H
diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h
index 3dcc6b6..43bf6a7 100644
--- a/libs/gui/include/gui/IProducerListener.h
+++ b/libs/gui/include/gui/IProducerListener.h
@@ -90,6 +90,9 @@
             Parcel* reply, uint32_t flags = 0);
     virtual bool needsReleaseNotify();
     virtual void onBuffersDiscarded(const std::vector<int32_t>& slots);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    virtual bool needsAttachNotify();
+#endif
 };
 
 #else
@@ -103,6 +106,9 @@
     virtual ~StubProducerListener();
     virtual void onBufferReleased() {}
     virtual bool needsReleaseNotify() { return false; }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    virtual bool needsAttachNotify() { return false; }
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index a836f46..9a422fd 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -18,6 +18,7 @@
 
 #include <android/gui/CachingHint.h>
 #include <android/gui/DisplayBrightness.h>
+#include <android/gui/DisplayCaptureArgs.h>
 #include <android/gui/FrameTimelineInfo.h>
 #include <android/gui/IDisplayEventConnection.h>
 #include <android/gui/IFpsListener.h>
@@ -27,6 +28,7 @@
 #include <android/gui/ITunnelModeEnabledListener.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <android/gui/IWindowInfosPublisher.h>
+#include <android/gui/LayerCaptureArgs.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
 #include <gui/ITransactionCompletedListener.h>
@@ -70,14 +72,6 @@
 using gui::IScreenCaptureListener;
 using gui::SpHash;
 
-namespace gui {
-
-struct DisplayCaptureArgs;
-struct LayerCaptureArgs;
-class LayerDebugInfo;
-
-} // namespace gui
-
 namespace ui {
 
 struct DisplayMode;
@@ -113,7 +107,7 @@
     /* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
     virtual status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -131,8 +125,8 @@
         CREATE_CONNECTION,               // Deprecated. Autogenerated by .aidl now.
         GET_STATIC_DISPLAY_INFO,         // Deprecated. Autogenerated by .aidl now.
         CREATE_DISPLAY_EVENT_CONNECTION, // Deprecated. Autogenerated by .aidl now.
-        CREATE_DISPLAY,                  // Deprecated. Autogenerated by .aidl now.
-        DESTROY_DISPLAY,                 // Deprecated. Autogenerated by .aidl now.
+        CREATE_VIRTUAL_DISPLAY,          // Deprecated. Autogenerated by .aidl now.
+        DESTROY_VIRTUAL_DISPLAY,         // Deprecated. Autogenerated by .aidl now.
         GET_PHYSICAL_DISPLAY_TOKEN,      // Deprecated. Autogenerated by .aidl now.
         SET_TRANSACTION_STATE,
         AUTHENTICATE_SURFACE,           // Deprecated. Autogenerated by .aidl now.
diff --git a/libs/gui/include/gui/ITransactionCompletedListener.h b/libs/gui/include/gui/ITransactionCompletedListener.h
index bc97cd0..014029b 100644
--- a/libs/gui/include/gui/ITransactionCompletedListener.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -16,8 +16,6 @@
 
 #pragma once
 
-#include "JankInfo.h"
-
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
@@ -40,15 +38,10 @@
 class CallbackId : public Parcelable {
 public:
     int64_t id;
-    enum class Type : int32_t {
-        ON_COMPLETE = 0,
-        ON_COMMIT = 1,
-        /*reserved for serialization = 2*/
-    } type;
-    bool includeJankData; // Only respected for ON_COMPLETE callbacks.
+    enum class Type : int32_t { ON_COMPLETE = 0, ON_COMMIT = 1 } type;
 
     CallbackId() {}
-    CallbackId(int64_t id, Type type) : id(id), type(type), includeJankData(false) {}
+    CallbackId(int64_t id, Type type) : id(id), type(type) {}
     status_t writeToParcel(Parcel* output) const override;
     status_t readFromParcel(const Parcel* input) override;
 
@@ -113,29 +106,6 @@
     nsecs_t dequeueReadyTime;
 };
 
-/**
- * Jank information representing SurfaceFlinger's jank classification about frames for a specific
- * surface.
- */
-class JankData : public Parcelable {
-public:
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-
-    JankData();
-    JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
-          : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
-
-    // Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
-    int64_t frameVsyncId;
-
-    // Bitmask of janks that occurred
-    int32_t jankType;
-
-    // Expected duration of the frame
-    nsecs_t frameIntervalNs;
-};
-
 class SurfaceStats : public Parcelable {
 public:
     status_t writeToParcel(Parcel* output) const override;
@@ -145,14 +115,13 @@
     SurfaceStats(const sp<IBinder>& sc, std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
                  const sp<Fence>& prevReleaseFence, std::optional<uint32_t> hint,
                  uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats,
-                 std::vector<JankData> jankData, ReleaseCallbackId previousReleaseCallbackId)
+                 ReleaseCallbackId previousReleaseCallbackId)
           : surfaceControl(sc),
             acquireTimeOrFence(std::move(acquireTimeOrFence)),
             previousReleaseFence(prevReleaseFence),
             transformHint(hint),
             currentMaxAcquiredBufferCount(currentMaxAcquiredBuffersCount),
             eventStats(frameEventStats),
-            jankData(std::move(jankData)),
             previousReleaseCallbackId(previousReleaseCallbackId) {}
 
     sp<IBinder> surfaceControl;
@@ -161,7 +130,6 @@
     std::optional<uint32_t> transformHint = 0;
     uint32_t currentMaxAcquiredBufferCount = 0;
     FrameEventHistoryStats eventStats;
-    std::vector<JankData> jankData;
     ReleaseCallbackId previousReleaseCallbackId;
 };
 
diff --git a/libs/gui/include/gui/InputTransferToken.h b/libs/gui/include/gui/InputTransferToken.h
new file mode 100644
index 0000000..fb4aaa7
--- /dev/null
+++ b/libs/gui/include/gui/InputTransferToken.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <binder/Binder.h>
+#include <binder/IBinder.h>
+#include <binder/Parcel.h>
+#include <private/gui/ParcelUtils.h>
+#include <utils/Errors.h>
+
+namespace android {
+struct InputTransferToken : public RefBase, Parcelable {
+public:
+    InputTransferToken() { mToken = sp<BBinder>::make(); }
+
+    InputTransferToken(const sp<IBinder>& token) { mToken = token; }
+
+    status_t writeToParcel(Parcel* parcel) const override {
+        SAFE_PARCEL(parcel->writeStrongBinder, mToken);
+        return NO_ERROR;
+    }
+
+    status_t readFromParcel(const Parcel* parcel) {
+        SAFE_PARCEL(parcel->readStrongBinder, &mToken);
+        return NO_ERROR;
+    };
+
+    sp<IBinder> mToken;
+};
+
+static inline bool operator==(const sp<InputTransferToken>& token1,
+                              const sp<InputTransferToken>& token2) {
+    if (token1.get() == token2.get()) {
+        return true;
+    }
+    return token1->mToken == token2->mToken;
+}
+
+} // namespace android
diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h
deleted file mode 100644
index fae2bcc..0000000
--- a/libs/gui/include/gui/LayerCaptureArgs.h
+++ /dev/null
@@ -1,34 +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 <stdint.h>
-#include <sys/types.h>
-
-#include <gui/DisplayCaptureArgs.h>
-
-namespace android::gui {
-
-struct LayerCaptureArgs : CaptureArgs {
-    sp<IBinder> layerHandle;
-    bool childrenOnly{false};
-
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-};
-
-}; // namespace android::gui
diff --git a/libs/gui/include/gui/LayerDebugInfo.h b/libs/gui/include/gui/LayerDebugInfo.h
deleted file mode 100644
index dbb80e5..0000000
--- a/libs/gui/include/gui/LayerDebugInfo.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <binder/Parcelable.h>
-
-#include <ui/PixelFormat.h>
-#include <ui/Region.h>
-#include <ui/StretchEffect.h>
-
-#include <string>
-#include <math/vec4.h>
-
-namespace android::gui {
-
-/* Class for transporting debug info from SurfaceFlinger to authorized
- * recipients.  The class is intended to be a data container. There are
- * no getters or setters.
- */
-class LayerDebugInfo : public Parcelable {
-public:
-    LayerDebugInfo() = default;
-    LayerDebugInfo(const LayerDebugInfo&) = default;
-    virtual ~LayerDebugInfo() = default;
-
-    virtual status_t writeToParcel(Parcel* parcel) const;
-    virtual status_t readFromParcel(const Parcel* parcel);
-
-    std::string mName = std::string("NOT FILLED");
-    std::string mParentName = std::string("NOT FILLED");
-    std::string mType = std::string("NOT FILLED");
-    Region mTransparentRegion = Region::INVALID_REGION;
-    Region mVisibleRegion = Region::INVALID_REGION;
-    Region mSurfaceDamageRegion = Region::INVALID_REGION;
-    uint32_t mLayerStack = 0;
-    float mX = 0.f;
-    float mY = 0.f;
-    uint32_t mZ = 0 ;
-    int32_t mWidth = -1;
-    int32_t mHeight = -1;
-    android::Rect mCrop = android::Rect::INVALID_RECT;
-    half4 mColor = half4(1.0_hf, 1.0_hf, 1.0_hf, 0.0_hf);
-    uint32_t mFlags = 0;
-    PixelFormat mPixelFormat = PIXEL_FORMAT_NONE;
-    android_dataspace mDataSpace = HAL_DATASPACE_UNKNOWN;
-    // Row-major transform matrix (SurfaceControl::setMatrix())
-    float mMatrix[2][2] = {{0.f, 0.f}, {0.f, 0.f}};
-    int32_t mActiveBufferWidth = -1;
-    int32_t mActiveBufferHeight = -1;
-    int32_t mActiveBufferStride = 0;
-    PixelFormat mActiveBufferFormat = PIXEL_FORMAT_NONE;
-    int32_t mNumQueuedFrames = -1;
-    bool mIsOpaque = false;
-    bool mContentDirty = false;
-    StretchEffect mStretchEffect = {};
-};
-
-std::string to_string(const LayerDebugInfo& info);
-
-} // namespace android::gui
diff --git a/libs/gui/include/gui/LayerMetadata.h b/libs/gui/include/gui/LayerMetadata.h
index 9cf62bc..7ee291d 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -74,6 +74,7 @@
 } // namespace android::gui
 
 using android::gui::METADATA_ACCESSIBILITY_ID;
+using android::gui::METADATA_CALLING_UID;
 using android::gui::METADATA_DEQUEUE_TIME;
 using android::gui::METADATA_GAME_MODE;
 using android::gui::METADATA_MOUSE_CURSOR;
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 0fedea7..2cdde32 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -21,7 +21,9 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <android/gui/DisplayCaptureArgs.h>
 #include <android/gui/IWindowInfosReportedListener.h>
+#include <android/gui/LayerCaptureArgs.h>
 #include <android/gui/TrustedPresentationThresholds.h>
 #include <android/native_window.h>
 #include <gui/IGraphicBufferProducer.h>
@@ -29,12 +31,13 @@
 #include <math/mat4.h>
 
 #include <android/gui/DropInputMode.h>
+#include <android/gui/EdgeExtensionParameters.h>
 #include <android/gui/FocusRequest.h>
+#include <android/gui/TrustedOverlay.h>
 
 #include <ftl/flags.h>
-#include <gui/DisplayCaptureArgs.h>
+#include <gui/BufferReleaseChannel.h>
 #include <gui/ISurfaceComposer.h>
-#include <gui/LayerCaptureArgs.h>
 #include <gui/LayerMetadata.h>
 #include <gui/SpHash.h>
 #include <gui/SurfaceControl.h>
@@ -127,6 +130,8 @@
 
     client_cache_t cachedBuffer;
 
+    nsecs_t dequeueTime;
+
     // Generates the release callback id based on the buffer id and frame number.
     // This is used as an identifier when release callbacks are invoked.
     ReleaseCallbackId generateReleaseCallbackId() const;
@@ -179,7 +184,6 @@
         eCachingHintChanged = 0x00000200,
         eDimmingEnabledChanged = 0x00000400,
         eShadowRadiusChanged = 0x00000800,
-        eRenderBorderChanged = 0x00001000,
         eBufferCropChanged = 0x00002000,
         eRelativeLayerChanged = 0x00004000,
         eReparent = 0x00008000,
@@ -216,6 +220,8 @@
         eTrustedOverlayChanged = 0x4000'00000000,
         eDropInputModeChanged = 0x8000'00000000,
         eExtendedRangeBrightnessChanged = 0x10000'00000000,
+        eEdgeExtensionChanged = 0x20000'00000000,
+        eBufferReleaseChannelChanged = 0x40000'00000000,
     };
 
     layer_state_t();
@@ -239,7 +245,7 @@
             layer_state_t::eCropChanged | layer_state_t::eDestinationFrameChanged |
             layer_state_t::eMatrixChanged | layer_state_t::ePositionChanged |
             layer_state_t::eTransformToDisplayInverseChanged |
-            layer_state_t::eTransparentRegionChanged;
+            layer_state_t::eTransparentRegionChanged | layer_state_t::eEdgeExtensionChanged;
 
     // Buffer and related updates.
     static constexpr uint64_t BUFFER_CHANGES = layer_state_t::eApiChanged |
@@ -258,8 +264,8 @@
             layer_state_t::eBlurRegionsChanged | layer_state_t::eColorChanged |
             layer_state_t::eColorSpaceAgnosticChanged | layer_state_t::eColorTransformChanged |
             layer_state_t::eCornerRadiusChanged | layer_state_t::eDimmingEnabledChanged |
-            layer_state_t::eHdrMetadataChanged | layer_state_t::eRenderBorderChanged |
-            layer_state_t::eShadowRadiusChanged | layer_state_t::eStretchChanged;
+            layer_state_t::eHdrMetadataChanged | layer_state_t::eShadowRadiusChanged |
+            layer_state_t::eStretchChanged;
 
     // Changes which invalidates the layer's visible region in CE.
     static constexpr uint64_t CONTENT_DIRTY = layer_state_t::CONTENT_CHANGES |
@@ -276,9 +282,9 @@
             layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
 
     // Changes affecting data sent to input.
-    static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
-            layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged |
-            layer_state_t::eLayerStackChanged;
+    static constexpr uint64_t INPUT_CHANGES = layer_state_t::eAlphaChanged |
+            layer_state_t::eInputInfoChanged | layer_state_t::eDropInputModeChanged |
+            layer_state_t::eTrustedOverlayChanged | layer_state_t::eLayerStackChanged;
 
     // Changes that affect the visible region on a display.
     static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
@@ -386,16 +392,14 @@
 
     // An inherited state that indicates that this surface control and its children
     // should be trusted for input occlusion detection purposes
-    bool isTrustedOverlay;
-
-    // Flag to indicate if border needs to be enabled on the layer
-    bool borderEnabled;
-    float borderWidth;
-    half4 borderColor;
+    gui::TrustedOverlay trustedOverlay;
 
     // Stretch effect to be applied to this layer
     StretchEffect stretchEffect;
 
+    // Edge extension effect to be applied to this layer
+    gui::EdgeExtensionParameters edgeExtensionParameters;
+
     Rect bufferCrop;
     Rect destinationFrame;
 
@@ -410,6 +414,8 @@
 
     TrustedPresentationThresholds trustedPresentationThresholds;
     TrustedPresentationListener trustedPresentationListener;
+
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
 };
 
 class ComposerState {
diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h
index a90f30c..b6588a2 100644
--- a/libs/gui/include/gui/LayerStatePermissions.h
+++ b/libs/gui/include/gui/LayerStatePermissions.h
@@ -15,15 +15,14 @@
  */
 
 #include <stdint.h>
-#include <string>
-#include <unordered_map>
-
+#include <utils/String16.h>
+#include <vector>
 namespace android {
 class LayerStatePermissions {
 public:
     static uint32_t getTransactionPermissions(int pid, int uid);
 
 private:
-    static std::unordered_map<std::string, int> mPermissionMap;
+    static std::vector<std::pair<String16, int>> mPermissionMap;
 };
 } // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/ScreenCaptureResults.h b/libs/gui/include/gui/ScreenCaptureResults.h
index 6e17791..f176f48 100644
--- a/libs/gui/include/gui/ScreenCaptureResults.h
+++ b/libs/gui/include/gui/ScreenCaptureResults.h
@@ -36,6 +36,11 @@
     bool capturedSecureLayers{false};
     bool capturedHdrLayers{false};
     ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB};
+    // A gainmap that can be used to "lift" the screenshot into HDR
+    sp<GraphicBuffer> optionalGainMap;
+    // HDR/SDR ratio value that fully applies the gainmap.
+    // Note that we use 1/64 epsilon offsets to eliminate precision issues
+    float hdrSdrRatio{1.0f};
 };
 
 } // namespace android::gui
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index 39a59e4..14a3513 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -18,6 +18,7 @@
 #define ANDROID_GUI_SURFACE_H
 
 #include <android/gui/FrameTimelineInfo.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferQueueDefs.h>
 #include <gui/HdrMetadata.h>
 #include <gui/IGraphicBufferProducer.h>
@@ -35,6 +36,8 @@
 
 namespace android {
 
+class GraphicBuffer;
+
 namespace gui {
 class ISurfaceComposer;
 } // namespace gui
@@ -56,8 +59,41 @@
     virtual bool needsReleaseNotify() = 0;
 
     virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& buffers) = 0;
+    virtual void onBufferDetached(int slot) = 0;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    virtual void onBufferAttached() {}
+    virtual bool needsAttachNotify() { return false; }
+#endif
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    // Called if this Surface is connected to a remote implementation and it
+    // dies or becomes unavailable.
+    virtual void onRemoteDied() {}
+
+    // Clients will overwrite this if they want to receive a notification
+    // via onRemoteDied. This should return a constant value.
+    virtual bool needsDeathNotify() { return false; }
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 };
 
+class StubSurfaceListener : public SurfaceListener {
+public:
+    virtual ~StubSurfaceListener() {}
+    virtual void onBufferReleased() override {}
+    virtual bool needsReleaseNotify() { return false; }
+    virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& /*buffers*/) override {}
+    virtual void onBufferDetached(int /*slot*/) override {}
+};
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+// Contains additional data from the queueBuffer operation.
+struct SurfaceQueueBufferOutput {
+    // True if this queueBuffer caused a buffer to be replaced in the queue
+    // (and therefore not will not be acquired)
+    bool bufferReplaced = false;
+};
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 /*
  * An implementation of ANativeWindow that feeds graphics buffers into a
  * BufferQueue.
@@ -154,6 +190,11 @@
      */
     virtual void allocateBuffers();
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    // See IGraphicBufferProducer::allowAllocation
+    status_t allowAllocation(bool allowAllocation);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
     /* Sets the generation number on the IGraphicBufferProducer and updates the
      * generation number on any buffers attached to the Surface after this call.
      * See IGBP::setGenerationNumber for more information. */
@@ -170,6 +211,14 @@
      * in <system/window.h>. */
     int setScalingMode(int mode);
 
+    virtual int setBuffersTimestamp(int64_t timestamp);
+    virtual int setBuffersDataSpace(ui::Dataspace dataSpace);
+    virtual int setCrop(Rect const* rect);
+    virtual int setBuffersTransform(uint32_t transform);
+    virtual int setBuffersStickyTransform(uint32_t transform);
+    virtual int setBuffersFormat(PixelFormat format);
+    virtual int setUsage(uint64_t reqUsage);
+
     // See IGraphicBufferProducer::setDequeueTimeout
     status_t setDequeueTimeout(nsecs_t timeout);
 
@@ -215,6 +264,16 @@
                                   int8_t changeFrameRateStrategy);
     virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_EXTENDEDALLOCATE)
+    /**
+     * Set additional options to be passed when allocating a buffer. Only valid if IAllocator-V2
+     * or newer is available, otherwise will return INVALID_OPERATION. Only allowed to be called
+     * after connect and options are cleared when disconnect happens. Returns NO_INIT if not
+     * connected
+     */
+    status_t setAdditionalOptions(const std::vector<gui::AdditionalOptions>& options);
+#endif
+
 protected:
     virtual ~Surface();
 
@@ -302,6 +361,7 @@
     int dispatchGetLastQueuedBuffer(va_list args);
     int dispatchGetLastQueuedBuffer2(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
+    int dispatchSetAdditionalOptions(va_list args);
 
     std::mutex mNameMutex;
     std::string mName;
@@ -310,7 +370,12 @@
 protected:
     virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
     virtual int cancelBuffer(ANativeWindowBuffer* buffer, int fenceFd);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    virtual int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd,
+                            SurfaceQueueBufferOutput* surfaceOutput = nullptr);
+#else
     virtual int queueBuffer(ANativeWindowBuffer* buffer, int fenceFd);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
     virtual int perform(int operation, va_list args);
     virtual int setSwapInterval(int interval);
 
@@ -319,16 +384,9 @@
     virtual int connect(int api);
     virtual int setBufferCount(int bufferCount);
     virtual int setBuffersUserDimensions(uint32_t width, uint32_t height);
-    virtual int setBuffersFormat(PixelFormat format);
-    virtual int setBuffersTransform(uint32_t transform);
-    virtual int setBuffersStickyTransform(uint32_t transform);
-    virtual int setBuffersTimestamp(int64_t timestamp);
-    virtual int setBuffersDataSpace(ui::Dataspace dataSpace);
     virtual int setBuffersSmpte2086Metadata(const android_smpte2086_metadata* metadata);
     virtual int setBuffersCta8613Metadata(const android_cta861_3_metadata* metadata);
     virtual int setBuffersHdr10PlusMetadata(const size_t size, const uint8_t* metadata);
-    virtual int setCrop(Rect const* rect);
-    virtual int setUsage(uint64_t reqUsage);
     virtual void setSurfaceDamage(android_native_rect_t* rects, size_t numRects);
 
 public:
@@ -346,22 +404,15 @@
     virtual int unlockAndPost();
     virtual int query(int what, int* value) const;
 
-    virtual int connect(int api, const sp<IProducerListener>& listener);
-
     // When reportBufferRemoval is true, clients must call getAndFlushRemovedBuffers to fetch
     // GraphicBuffers removed from this surface after a dequeueBuffer, detachNextBuffer or
     // attachBuffer call. This allows clients with their own buffer caches to free up buffers no
     // longer in use by this surface.
-    virtual int connect(
-            int api, const sp<IProducerListener>& listener,
-            bool reportBufferRemoval);
-    virtual int detachNextBuffer(sp<GraphicBuffer>* outBuffer,
-            sp<Fence>* outFence);
+    virtual int connect(int api, const sp<SurfaceListener>& listener,
+                        bool reportBufferRemoval = false);
+    virtual int detachNextBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence);
     virtual int attachBuffer(ANativeWindowBuffer*);
 
-    virtual int connect(
-            int api, bool reportBufferRemoval,
-            const sp<SurfaceListener>& sListener);
     virtual void destroy();
 
     // When client connects to Surface with reportBufferRemoval set to true, any buffers removed
@@ -376,6 +427,21 @@
     static status_t attachAndQueueBufferWithDataspace(Surface* surface, sp<GraphicBuffer> buffer,
                                                       ui::Dataspace dataspace);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    // Dequeues a buffer and its outFence, which must be signalled before the buffer can be used.
+    status_t dequeueBuffer(sp<GraphicBuffer>* buffer, sp<Fence>* outFence);
+
+    // Queues a buffer, with an optional fd fence that captures pending work on the buffer. This
+    // buffer must have been returned by dequeueBuffer or associated with this Surface via an
+    // attachBuffer operation.
+    status_t queueBuffer(const sp<GraphicBuffer>& buffer, const sp<Fence>& fd = Fence::NO_FENCE,
+                         SurfaceQueueBufferOutput* output = nullptr);
+
+    // Detaches this buffer, dissociating it from this Surface. This buffer must have been returned
+    // by queueBuffer or associated with this Surface via an attachBuffer operation.
+    status_t detachBuffer(const sp<GraphicBuffer>& buffer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
     // Batch version of dequeueBuffer, cancelBuffer and queueBuffer
     // Note that these batched operations are not supported when shared buffer mode is being used.
     struct BatchBuffer {
@@ -390,8 +456,13 @@
         int fenceFd = -1;
         nsecs_t timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
     };
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    virtual int queueBuffers(const std::vector<BatchQueuedBuffer>& buffers,
+                             std::vector<SurfaceQueueBufferOutput>* queueBufferOutputs = nullptr);
+#else
     virtual int queueBuffers(
             const std::vector<BatchQueuedBuffer>& buffers);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
 
 protected:
     enum { NUM_BUFFER_SLOTS = BufferQueueDefs::NUM_BUFFER_SLOTS };
@@ -411,12 +482,38 @@
             return mSurfaceListener->needsReleaseNotify();
         }
 
+        virtual void onBufferDetached(int slot) { mSurfaceListener->onBufferDetached(slot); }
+
         virtual void onBuffersDiscarded(const std::vector<int32_t>& slots);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+        virtual void onBufferAttached() {
+            mSurfaceListener->onBufferAttached();
+        }
+
+        virtual bool needsAttachNotify() {
+            return mSurfaceListener->needsAttachNotify();
+        }
+#endif
     private:
         wp<Surface> mParent;
         sp<SurfaceListener> mSurfaceListener;
     };
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    class ProducerDeathListenerProxy : public IBinder::DeathRecipient {
+    public:
+        ProducerDeathListenerProxy(wp<SurfaceListener> surfaceListener);
+        ProducerDeathListenerProxy(ProducerDeathListenerProxy&) = delete;
+
+        // IBinder::DeathRecipient
+        virtual void binderDied(const wp<IBinder>&) override;
+
+    private:
+        wp<SurfaceListener> mSurfaceListener;
+    };
+    friend class ProducerDeathListenerProxy;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
     void querySupportedTimestampsLocked() const;
 
     void freeAllBuffers();
@@ -448,6 +545,13 @@
     // TODO: rename to mBufferProducer
     sp<IGraphicBufferProducer> mGraphicBufferProducer;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+    // mSurfaceDeathListener gets registered as mGraphicBufferProducer's
+    // DeathRecipient when SurfaceListener::needsDeathNotify returns true and
+    // gets notified when it dies.
+    sp<ProducerDeathListenerProxy> mSurfaceDeathListener;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
     // mSlots stores the buffers that have been allocated for each buffer slot.
     // It is initialized to null pointers, and gets filled in with the result of
     // IGraphicBufferProducer::requestBuffer when the client dequeues a buffer from a
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 2888826..4f9af16 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -18,6 +18,7 @@
 
 #include <stdint.h>
 #include <sys/types.h>
+
 #include <set>
 #include <thread>
 #include <unordered_map>
@@ -34,14 +35,17 @@
 #include <ui/BlurRegion.h>
 #include <ui/ConfigStoreTypes.h>
 #include <ui/DisplayedFrameStats.h>
+#include <ui/EdgeExtensionEffect.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicTypes.h>
 #include <ui/PixelFormat.h>
 #include <ui/Rotation.h>
 #include <ui/StaticDisplayInfo.h>
 
+#include <android/gui/BnJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
 
+#include <gui/BufferReleaseChannel.h>
 #include <gui/CpuConsumer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/ITransactionCompletedListener.h>
@@ -336,6 +340,8 @@
     static std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>
     getDisplayDecorationSupport(const sp<IBinder>& displayToken);
 
+    static bool flagEdgeExtensionEffectUseShader();
+
     // ------------------------------------------------------------------------
     // surface creation / destruction
 
@@ -374,17 +380,15 @@
 
     sp<SurfaceControl> mirrorDisplay(DisplayId displayId);
 
-    //! Create a virtual display
-    static sp<IBinder> createDisplay(const String8& displayName, bool secure,
-                                     float requestedRefereshRate = 0);
+    static const std::string kEmpty;
+    static sp<IBinder> createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                            const std::string& uniqueId = kEmpty,
+                                            float requestedRefreshRate = 0);
 
-    //! Destroy a virtual display
-    static void destroyDisplay(const sp<IBinder>& display);
+    static status_t destroyVirtualDisplay(const sp<IBinder>& displayToken);
 
-    //! Get stable IDs for connected physical displays
     static std::vector<PhysicalDisplayId> getPhysicalDisplayIds();
 
-    //! Get token for a physical display given its stable ID
     static sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId);
 
     // Returns StalledTransactionInfo if a transaction from the provided pid has not been applied
@@ -431,6 +435,8 @@
         static std::mutex sApplyTokenMutex;
         void releaseBufferIfOverwriting(const layer_state_t& state);
         static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
+        // Tracks registered callbacks
+        sp<TransactionCompletedListener> mTransactionCompletedListener = nullptr;
 
     protected:
         std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
@@ -446,7 +452,6 @@
 
         uint64_t mId;
 
-        uint32_t mTransactionNestCount = 0;
         bool mAnimation = false;
         bool mEarlyWakeupStart = false;
         bool mEarlyWakeupEnd = false;
@@ -567,7 +572,8 @@
         Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
                                const std::optional<sp<Fence>>& fence = std::nullopt,
                                const std::optional<uint64_t>& frameNumber = std::nullopt,
-                               uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
+                               uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr,
+                               nsecs_t dequeueTime = -1);
         Transaction& unsetBuffer(const sp<SurfaceControl>& sc);
         std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
 
@@ -718,6 +724,8 @@
 
         // Sets that this surface control and its children are trusted overlays for input
         Transaction& setTrustedOverlay(const sp<SurfaceControl>& sc, bool isTrustedOverlay);
+        Transaction& setTrustedOverlay(const sp<SurfaceControl>& sc,
+                                       gui::TrustedOverlay trustedOverlay);
 
         // Queues up transactions using this token in SurfaceFlinger.  By default, all transactions
         // from a client are placed on the same queue. This can be used to prevent multiple
@@ -739,13 +747,25 @@
         Transaction& setStretchEffect(const sp<SurfaceControl>& sc,
                                       const StretchEffect& stretchEffect);
 
+        /**
+         * Provides the edge extension effect configured on a container that the
+         * surface is rendered within.
+         * @param sc target surface the edge extension should be applied to
+         * @param effect the corresponding EdgeExtensionParameters to be applied
+         *    to the surface.
+         * @return The transaction being constructed
+         */
+        Transaction& setEdgeExtensionEffect(const sp<SurfaceControl>& sc,
+                                            const gui::EdgeExtensionParameters& effect);
+
         Transaction& setBufferCrop(const sp<SurfaceControl>& sc, const Rect& bufferCrop);
         Transaction& setDestinationFrame(const sp<SurfaceControl>& sc,
                                          const Rect& destinationFrame);
         Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
 
-        Transaction& enableBorder(const sp<SurfaceControl>& sc, bool shouldEnable, float width,
-                                  const half4& color);
+        Transaction& setBufferReleaseChannel(
+                const sp<SurfaceControl>& sc,
+                const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel);
 
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
@@ -826,6 +846,8 @@
                     nullptr);
     status_t removeWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener);
 
+    static void notifyShutdown();
+
 protected:
     ReleaseCallbackThread mReleaseCallbackThread;
 
@@ -861,12 +883,82 @@
 
 // ---------------------------------------------------------------------------
 
-class JankDataListener : public VirtualLightRefBase {
+class JankDataListener;
+
+// Acts as a representative listener to the composer for a single layer and
+// forwards any received jank data to multiple listeners. Will remove itself
+// from the composer only once the last listener is removed.
+class JankDataListenerFanOut : public gui::BnJankListener {
 public:
-    virtual ~JankDataListener() = 0;
-    virtual void onJankDataAvailable(const std::vector<JankData>& jankData) = 0;
+    JankDataListenerFanOut(int32_t layerId) : mLayerId(layerId) {}
+
+    binder::Status onJankData(const std::vector<gui::JankData>& jankData) override;
+
+    static status_t addListener(sp<SurfaceControl> sc, sp<JankDataListener> listener);
+    static status_t removeListener(sp<JankDataListener> listener);
+
+private:
+    std::vector<sp<JankDataListener>> getActiveListeners();
+    bool removeListeners(const std::vector<wp<JankDataListener>>& listeners);
+    int64_t updateAndGetRemovalVSync();
+
+    struct WpJDLHash {
+        std::size_t operator()(const wp<JankDataListener>& listener) const {
+            return std::hash<JankDataListener*>{}(listener.unsafe_get());
+        }
+    };
+
+    std::mutex mMutex;
+    std::unordered_set<wp<JankDataListener>, WpJDLHash> mListeners GUARDED_BY(mMutex);
+    int32_t mLayerId;
+    int64_t mRemoveAfter = -1;
+
+    static std::mutex sFanoutInstanceMutex;
+    static std::unordered_map<int32_t, sp<JankDataListenerFanOut>> sFanoutInstances;
 };
 
+// Base class for client listeners interested in jank classification data from
+// the composer. Subclasses should override onJankDataAvailable and call the add
+// and removal methods to receive jank data.
+class JankDataListener : public virtual RefBase {
+public:
+    JankDataListener() {}
+    virtual ~JankDataListener();
+
+    virtual bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) = 0;
+
+    status_t addListener(sp<SurfaceControl> sc) {
+        if (mLayerId != -1) {
+            removeListener(0);
+            mLayerId = -1;
+        }
+
+        int32_t layerId = sc->getLayerId();
+        status_t status =
+                JankDataListenerFanOut::addListener(std::move(sc),
+                                                    sp<JankDataListener>::fromExisting(this));
+        if (status == OK) {
+            mLayerId = layerId;
+        }
+        return status;
+    }
+
+    status_t removeListener(int64_t afterVsync) {
+        mRemoveAfter = std::max(static_cast<int64_t>(0), afterVsync);
+        return JankDataListenerFanOut::removeListener(sp<JankDataListener>::fromExisting(this));
+    }
+
+    status_t flushJankData();
+
+    friend class JankDataListenerFanOut;
+
+private:
+    int32_t mLayerId = -1;
+    int64_t mRemoveAfter = -1;
+};
+
+// ---------------------------------------------------------------------------
+
 class TransactionCompletedListener : public BnTransactionCompletedListener {
 public:
     TransactionCompletedListener();
@@ -901,7 +993,6 @@
 
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> mCallbacks
             GUARDED_BY(mMutex);
-    std::multimap<int32_t, sp<JankDataListener>> mJankListeners GUARDED_BY(mMutex);
     std::unordered_map<ReleaseCallbackId, ReleaseBufferCallback, ReleaseBufferCallbackIdHash>
             mReleaseBufferCallbacks GUARDED_BY(mMutex);
 
@@ -924,14 +1015,10 @@
             const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
                     surfaceControls,
             CallbackId::Type callbackType);
-    CallbackId addCallbackFunctionLocked(
-            const TransactionCompletedCallback& callbackFunction,
-            const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
-                    surfaceControls,
-            CallbackId::Type callbackType) REQUIRES(mMutex);
 
-    void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo,
-                                      const sp<SurfaceControl>& surfaceControl);
+    void addSurfaceControlToCallbacks(
+            const sp<SurfaceControl>& surfaceControl,
+            const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
 
     void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
     void removeQueueStallListener(void *id);
@@ -940,18 +1027,6 @@
             TrustedPresentationCallback tpc, int id, void* context);
     void clearTrustedPresentationCallback(int id);
 
-    /*
-     * Adds a jank listener to be informed about SurfaceFlinger's jank classification for a specific
-     * surface. Jank classifications arrive as part of the transaction callbacks about previous
-     * frames submitted to this Surface.
-     */
-    void addJankListener(const sp<JankDataListener>& listener, sp<SurfaceControl> surfaceControl);
-
-    /**
-     * Removes a jank listener previously added to addJankCallback.
-     */
-    void removeJankListener(const sp<JankDataListener>& listener);
-
     void addSurfaceStatsListener(void* context, void* cookie, sp<SurfaceControl> surfaceControl,
                 SurfaceStatsCallback listener);
     void removeSurfaceStatsListener(void* context, void* cookie);
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 32d60be..eb3be55 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -23,7 +23,7 @@
 #include <ftl/flags.h>
 #include <ftl/mixins.h>
 #include <gui/PidUid.h>
-#include <gui/constants.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
 #include <ui/Size.h>
@@ -178,6 +178,8 @@
                 static_cast<uint32_t>(os::InputConfig::CLONE),
         GLOBAL_STYLUS_BLOCKS_TOUCH =
                 static_cast<uint32_t>(os::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH),
+        SENSITIVE_FOR_PRIVACY =
+                static_cast<uint32_t>(os::InputConfig::SENSITIVE_FOR_PRIVACY),
         // clang-format on
     };
 
@@ -232,7 +234,7 @@
     Uid ownerUid = Uid::INVALID;
     std::string packageName;
     ftl::Flags<InputConfig> inputConfig;
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     InputApplicationInfo applicationInfo;
     bool replaceTouchableRegionWithCrop = false;
     wp<IBinder> touchableRegionCropHandle;
@@ -254,10 +256,6 @@
 
     void addTouchableRegion(const Rect& region);
 
-    bool touchableRegionContainsPoint(int32_t x, int32_t y) const;
-
-    bool frameContainsPoint(int32_t x, int32_t y) const;
-
     bool supportsSplitTouch() const;
 
     bool isSpy() const;
diff --git a/libs/gui/include/gui/constants.h b/libs/gui/include/gui/constants.h
deleted file mode 100644
index 8eab378..0000000
--- a/libs/gui/include/gui/constants.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <stdint.h>
-
-namespace android {
-
-/**
- * Invalid value for display size. Used when display size isn't available.
- */
-constexpr int32_t INVALID_DISPLAY_SIZE = 0;
-
-enum {
-    /* Used when an event is not associated with any display.
-     * Typically used for non-pointer events. */
-    ADISPLAY_ID_NONE = -1,
-
-    /* The default display id. */
-    ADISPLAY_ID_DEFAULT = 0,
-};
-
-} // namespace android
\ No newline at end of file
diff --git a/libs/gui/include/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index b7aba2b..7ddac81 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -59,8 +59,9 @@
     // of the full parceling to happen on its native side.
     status_t readFromParcel(const Parcel* parcel, bool nameAlreadyRead);
 
-  private:
+    std::string toString() const;
 
+private:
     static String16 readMaybeEmptyString16(const Parcel* parcel);
 };
 
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index 78fc590..d3f2899 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -7,7 +7,7 @@
   description: "This flag controls plumbing setFrameRate thru BufferQueue"
   bug: "281695725"
   is_fixed_read_only: true
-}
+} # bq_setframerate
 
 flag {
   name: "bq_consumer_attach_callback"
@@ -23,4 +23,95 @@
   description: "Controls a fence fixup for timestamp apis"
   bug: "310927247"
   is_fixed_read_only: true
-}
+} # frametimestamps_previousrelease
+
+flag {
+  name: "bq_extendedallocate"
+  namespace: "core_graphics"
+  description: "Add BQ support for allocate with extended options"
+  bug: "268382490"
+  is_fixed_read_only: true
+} # bq_extendedallocate
+
+flag {
+  name: "trace_frame_rate_override"
+  namespace: "core_graphics"
+  description: "Trace FrameRateOverride fps"
+  bug: "347314033"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # trace_frame_rate_override
+
+flag {
+  name: "wb_consumer_base_owns_bq"
+  namespace: "core_graphics"
+  description: "ConsumerBase-based classes now own their own bufferqueue"
+  bug: "340933754"
+  is_fixed_read_only: true
+} # wb_consumer_base_owns_bq
+
+flag {
+  name: "wb_platform_api_improvements"
+  namespace: "core_graphics"
+  description: "Simple improvements to Surface and ConsumerBase classes"
+  bug: "340933794"
+  is_fixed_read_only: true
+} # wb_platform_api_improvements
+
+flag {
+    name: "wb_stream_splitter"
+    namespace: "core_graphics"
+    description: "Removes IGBP/IGBCs from Camera3StreamSplitter as part of BufferQueue refactors"
+    bug: "340933206"
+    is_fixed_read_only: true
+} # wb_stream_splitter
+
+flag {
+  name: "edge_extension_shader"
+  namespace: "windowing_frontend"
+  description: "Enable edge extension via shader"
+  bug: "322036393"
+  is_fixed_read_only: true
+} # edge_extension_shader
+
+flag {
+  name: "buffer_release_channel"
+  namespace: "window_surfaces"
+  description: "Enable BufferReleaseChannel to optimize buffer releases"
+  bug: "294133380"
+  is_fixed_read_only: true
+} # buffer_release_channel
+
+flag {
+  name: "wb_ring_buffer"
+  namespace: "core_graphics"
+  description: "Remove slot dependency in the Ring Buffer Consumer."
+  bug: "342197847"
+  is_fixed_read_only: true
+} # wb_ring_buffer
+
+flag {
+  name: "wb_camera3_and_processors"
+  namespace: "core_graphics"
+  description: "Remove usage of IGBPs in the *Processor and Camera3*"
+  bug: "342199002"
+  is_fixed_read_only: true
+} # wb_camera3_and_processors
+
+flag {
+  name: "wb_libcameraservice"
+  namespace: "core_graphics"
+  description: "Remove usage of IGBPs in the libcameraservice."
+  bug: "342197849"
+  is_fixed_read_only: true
+} # wb_libcameraservice
+
+flag {
+  name: "bq_producer_throttles_only_async_mode"
+  namespace: "core_graphics"
+  description: "BufferQueueProducer only CPU throttle on queueBuffer() in async mode."
+  bug: "359252619"
+  is_fixed_read_only: true
+} # bq_producer_throttles_only_async_mode
diff --git a/libs/gui/rust/aidl_types/Android.bp b/libs/gui/rust/aidl_types/Android.bp
new file mode 100644
index 0000000..794f69e
--- /dev/null
+++ b/libs/gui/rust/aidl_types/Android.bp
@@ -0,0 +1,23 @@
+rust_defaults {
+    name: "libgui_aidl_types_defaults",
+    srcs: ["src/lib.rs"],
+    rustlibs: [
+        "libbinder_rs",
+    ],
+}
+
+rust_library {
+    name: "libgui_aidl_types_rs",
+    crate_name: "gui_aidl_types_rs",
+    defaults: ["libgui_aidl_types_defaults"],
+
+    // Currently necessary for host builds
+    // TODO(b/31559095): bionic on host should define this
+    target: {
+        darwin: {
+            enabled: false,
+        },
+    },
+    min_sdk_version: "VanillaIceCream",
+    vendor_available: true,
+}
diff --git a/libs/gui/rust/aidl_types/src/lib.rs b/libs/gui/rust/aidl_types/src/lib.rs
new file mode 100644
index 0000000..2351df0
--- /dev/null
+++ b/libs/gui/rust/aidl_types/src/lib.rs
@@ -0,0 +1,52 @@
+// Copyright (C) 2024 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.
+
+//! Rust wrapper for libgui AIDL types.
+
+use binder::{
+    binder_impl::{BorrowedParcel, UnstructuredParcelable},
+    impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+    StatusCode,
+};
+
+macro_rules! stub_unstructured_parcelable {
+    ($name:ident) => {
+        /// Unimplemented stub parcelable.
+        #[derive(Debug, Default)]
+        pub struct $name(());
+
+        impl UnstructuredParcelable for $name {
+            fn write_to_parcel(&self, _parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+                todo!()
+            }
+
+            fn from_parcel(_parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+                todo!()
+            }
+        }
+
+        impl_deserialize_for_unstructured_parcelable!($name);
+        impl_serialize_for_unstructured_parcelable!($name);
+    };
+}
+
+stub_unstructured_parcelable!(BitTube);
+stub_unstructured_parcelable!(DisplayInfo);
+stub_unstructured_parcelable!(LayerDebugInfo);
+stub_unstructured_parcelable!(LayerMetadata);
+stub_unstructured_parcelable!(ParcelableVsyncEventData);
+stub_unstructured_parcelable!(ScreenCaptureResults);
+stub_unstructured_parcelable!(VsyncEventData);
+stub_unstructured_parcelable!(WindowInfo);
+stub_unstructured_parcelable!(WindowInfosUpdate);
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index e606b99..f07747f 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -12,6 +12,34 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+aidl_interface {
+    name: "libgui_test_server_aidl",
+    unstable: true,
+    srcs: ["testserver/aidl/**/*.aidl"],
+    local_include_dir: "testserver/aidl",
+    include_dirs: [
+        "frameworks/native/aidl/gui",
+    ],
+    backend: {
+        cpp: {
+            enabled: true,
+            additional_shared_libraries: [
+                "libgui",
+                "libui",
+            ],
+        },
+        java: {
+            enabled: false,
+        },
+        ndk: {
+            enabled: false,
+        },
+        rust: {
+            enabled: false,
+        },
+    },
+}
+
 cc_test {
     name: "libgui_test",
     test_suites: ["device-tests"],
@@ -21,35 +49,45 @@
     cppflags: [
         "-Wall",
         "-Werror",
-        "-Wno-extra",
+        "-Wextra",
+        "-Wthread-safety",
         "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_EXTENDEDALLOCATE=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_CONSUMER_BASE_OWNS_BQ=true",
+        "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_WB_PLATFORM_API_IMPROVEMENTS=true",
     ],
 
     srcs: [
-        "LibGuiMain.cpp", // Custom gtest entrypoint
         "BLASTBufferQueue_test.cpp",
         "BufferItemConsumer_test.cpp",
         "BufferQueue_test.cpp",
+        "BufferReleaseChannel_test.cpp",
+        "Choreographer_test.cpp",
         "CompositorTiming_test.cpp",
         "CpuConsumer_test.cpp",
-        "EndToEndNativeInputTest.cpp",
-        "FrameRateUtilsTest.cpp",
-        "DisplayInfo_test.cpp",
         "DisplayedContentSampling_test.cpp",
+        "DisplayInfo_test.cpp",
+        "EndToEndNativeInputTest.cpp",
         "FillBuffer.cpp",
+        "FrameRateUtilsTest.cpp",
         "GLTest.cpp",
         "IGraphicBufferProducer_test.cpp",
+        "LibGuiMain.cpp", // Custom gtest entrypoint
         "Malicious.cpp",
         "MultiTextureConsumer_test.cpp",
         "RegionSampling_test.cpp",
         "StreamSplitter_test.cpp",
+        "Surface_test.cpp",
         "SurfaceTextureClient_test.cpp",
         "SurfaceTextureFBO_test.cpp",
+        "SurfaceTextureGL_test.cpp",
         "SurfaceTextureGLThreadToGL_test.cpp",
         "SurfaceTextureGLToGL_test.cpp",
-        "SurfaceTextureGL_test.cpp",
         "SurfaceTextureMultiContextGL_test.cpp",
-        "Surface_test.cpp",
+        "TestServer_test.cpp",
+        "testserver/TestServer.cpp",
+        "testserver/TestServerClient.cpp",
+        "testserver/TestServerHost.cpp",
         "TextureRenderer.cpp",
         "VsyncEventData_test.cpp",
         "WindowInfo_test.cpp",
@@ -60,7 +98,15 @@
         "android.hardware.configstore-utils",
         "libSurfaceFlingerProp",
         "libGLESv1_CM",
+        "libgui_test_server_aidl-cpp",
         "libinput",
+        "libnativedisplay",
+    ],
+
+    // This needs to get copied over for the test since it's not part of the
+    // platform.
+    data_libs: [
+        "libgui_test_server_aidl-cpp",
     ],
 
     static_libs: [
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index ea7078d..53f4a36 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -18,8 +18,9 @@
 
 #include <gui/BLASTBufferQueue.h>
 
+#include <android-base/thread_annotations.h>
 #include <android/hardware/graphics/common/1.2/types.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/BufferQueueCore.h>
 #include <gui/BufferQueueProducer.h>
 #include <gui/FrameTimestamps.h>
@@ -61,7 +62,8 @@
     }
 
     void waitOnNumberReleased(int32_t expectedNumReleased) {
-        std::unique_lock<std::mutex> lock(mMutex);
+        std::unique_lock lock{mMutex};
+        base::ScopedLockAssertion assumeLocked(mMutex);
         while (mNumReleased < expectedNumReleased) {
             ASSERT_NE(mReleaseCallback.wait_for(lock, std::chrono::seconds(3)),
                       std::cv_status::timeout)
@@ -134,11 +136,18 @@
 
     void clearSyncTransaction() { mBlastBufferQueueAdapter->clearSyncTransaction(); }
 
-    int getWidth() { return mBlastBufferQueueAdapter->mSize.width; }
+    int getWidth() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.width;
+    }
 
-    int getHeight() { return mBlastBufferQueueAdapter->mSize.height; }
+    int getHeight() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
+        return mBlastBufferQueueAdapter->mSize.height;
+    }
 
     std::function<void(Transaction*)> getTransactionReadyCallback() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mTransactionReadyCallback;
     }
 
@@ -147,6 +156,7 @@
     }
 
     const sp<SurfaceControl> getSurfaceControl() {
+        std::scoped_lock lock(mBlastBufferQueueAdapter->mMutex);
         return mBlastBufferQueueAdapter->mSurfaceControl;
     }
 
@@ -156,6 +166,7 @@
 
     void waitForCallbacks() {
         std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+        base::ScopedLockAssertion assumeLocked(mBlastBufferQueueAdapter->mMutex);
         // Wait until all but one of the submitted buffers have been released.
         while (mBlastBufferQueueAdapter->mSubmitted.size() > 1) {
             mBlastBufferQueueAdapter->mCallbackCV.wait(lock);
@@ -166,8 +177,8 @@
         mBlastBufferQueueAdapter->waitForCallback(frameNumber);
     }
 
-    void validateNumFramesSubmitted(int64_t numFramesSubmitted) {
-        std::unique_lock lock{mBlastBufferQueueAdapter->mMutex};
+    void validateNumFramesSubmitted(size_t numFramesSubmitted) {
+        std::scoped_lock lock{mBlastBufferQueueAdapter->mMutex};
         ASSERT_EQ(numFramesSubmitted, mBlastBufferQueueAdapter->mSubmitted.size());
     }
 
@@ -175,6 +186,10 @@
         mBlastBufferQueueAdapter->mergeWithNextTransaction(merge, frameNumber);
     }
 
+    void setApplyToken(sp<IBinder> applyToken) {
+        mBlastBufferQueueAdapter->setApplyToken(std::move(applyToken));
+    }
+
 private:
     sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
@@ -201,7 +216,7 @@
         mDisplayWidth = resolution.getWidth();
         mDisplayHeight = resolution.getHeight();
         ALOGD("Display: %dx%d orientation:%d", mDisplayWidth, mDisplayHeight,
-              displayState.orientation);
+              static_cast<int32_t>(displayState.orientation));
 
         mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
                                                      mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
@@ -218,7 +233,8 @@
                                                  ISurfaceComposerClient::eFXSurfaceBufferState,
                                                  /*parent*/ mRootSurfaceControl->getHandle());
 
-        mCaptureArgs.sourceCrop = Rect(ui::Size(mDisplayWidth, mDisplayHeight));
+        mCaptureArgs.captureArgs.sourceCrop =
+                gui::aidl_utils::toARect(mDisplayWidth, mDisplayHeight);
         mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
     }
 
@@ -240,8 +256,8 @@
 
     void fillBuffer(uint32_t* bufData, Rect rect, uint32_t stride, uint8_t r, uint8_t g,
                     uint8_t b) {
-        for (uint32_t row = rect.top; row < rect.bottom; row++) {
-            for (uint32_t col = rect.left; col < rect.right; col++) {
+        for (int32_t row = rect.top; row < rect.bottom; row++) {
+            for (int32_t col = rect.left; col < rect.right; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 *pixel = r;
                 *(pixel + 1) = g;
@@ -271,16 +287,16 @@
                             bool outsideRegion = false) {
         sp<GraphicBuffer>& captureBuf = mCaptureResults.buffer;
         const auto epsilon = 3;
-        const auto width = captureBuf->getWidth();
-        const auto height = captureBuf->getHeight();
+        const int32_t width = static_cast<int32_t>(captureBuf->getWidth());
+        const int32_t height = static_cast<int32_t>(captureBuf->getHeight());
         const auto stride = captureBuf->getStride();
 
         uint32_t* bufData;
         captureBuf->lock(static_cast<uint32_t>(GraphicBuffer::USAGE_SW_READ_OFTEN),
                          reinterpret_cast<void**>(&bufData));
 
-        for (uint32_t row = 0; row < height; row++) {
-            for (uint32_t col = 0; col < width; col++) {
+        for (int32_t row = 0; row < height; row++) {
+            for (int32_t col = 0; col < width; col++) {
                 uint8_t* pixel = (uint8_t*)(bufData + (row * stride) + col);
                 ASSERT_NE(nullptr, pixel);
                 bool inRegion;
@@ -352,8 +368,8 @@
     // create BLASTBufferQueue adapter associated with this surface
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
     ASSERT_EQ(mSurfaceControl, adapter.getSurfaceControl());
-    ASSERT_EQ(mDisplayWidth, adapter.getWidth());
-    ASSERT_EQ(mDisplayHeight, adapter.getHeight());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth), adapter.getWidth());
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight), adapter.getHeight());
     ASSERT_EQ(nullptr, adapter.getTransactionReadyCallback());
 }
 
@@ -371,10 +387,10 @@
 
     int32_t width;
     igbProducer->query(NATIVE_WINDOW_WIDTH, &width);
-    ASSERT_EQ(mDisplayWidth / 2, width);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayWidth) / 2, width);
     int32_t height;
     igbProducer->query(NATIVE_WINDOW_HEIGHT, &height);
-    ASSERT_EQ(mDisplayHeight / 2, height);
+    ASSERT_EQ(static_cast<int32_t>(mDisplayHeight) / 2, height);
 }
 
 TEST_F(BLASTBufferQueueTest, SyncNextTransaction) {
@@ -476,7 +492,7 @@
         ASSERT_EQ(OK, igbProducer->requestBuffer(slot, &buf));
         allocated.push_back({slot, fence});
     }
-    for (int i = 0; i < allocated.size(); i++) {
+    for (size_t i = 0; i < allocated.size(); i++) {
         igbProducer->cancelBuffer(allocated[i].first, allocated[i].second);
     }
 
@@ -499,6 +515,69 @@
     adapter.waitForCallbacks();
 }
 
+class WaitForCommittedCallback {
+public:
+    WaitForCommittedCallback() = default;
+    ~WaitForCommittedCallback() = default;
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+        mCallbackReceivedTimeStamp = std::chrono::system_clock::now();
+    }
+    auto getCallback() {
+        return [this](void* /* unused context */, nsecs_t /* latchTime */,
+                      const sp<Fence>& /* presentFence */,
+                      const std::vector<SurfaceControlStats>& /* stats */) { notify(); };
+    }
+    std::chrono::time_point<std::chrono::system_clock> mCallbackReceivedTimeStamp;
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST_F(BLASTBufferQueueTest, setApplyToken) {
+    sp<IBinder> applyToken = sp<BBinder>::make();
+    WaitForCommittedCallback firstTransaction;
+    WaitForCommittedCallback secondTransaction;
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(firstTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127,
+                    /*presentTimeDelay*/ std::chrono::nanoseconds(500ms).count());
+    }
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(secondTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127, /*presentTimeDelay*/ 0);
+    }
+
+    firstTransaction.wait();
+    secondTransaction.wait();
+    EXPECT_GT(secondTransaction.mCallbackReceivedTimeStamp,
+              firstTransaction.mCallbackReceivedTimeStamp);
+}
+
 TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
     uint8_t r = 255;
     uint8_t g = 0;
@@ -1258,6 +1337,20 @@
     }
 };
 
+class TestSurfaceListener : public SurfaceListener {
+public:
+    sp<IGraphicBufferProducer> mIgbp;
+    TestSurfaceListener(const sp<IGraphicBufferProducer>& igbp) : mIgbp(igbp) {}
+    void onBufferReleased() override {
+        sp<GraphicBuffer> buffer;
+        sp<Fence> fence;
+        mIgbp->detachNextBuffer(&buffer, &fence);
+    }
+    bool needsReleaseNotify() override { return true; }
+    void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& /*buffers*/) override {}
+    void onBufferDetached(int /*slot*/) {}
+};
+
 TEST_F(BLASTBufferQueueTest, CustomProducerListener) {
     BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
     sp<IGraphicBufferProducer> igbProducer = adapter.getIGraphicBufferProducer();
@@ -1313,14 +1406,14 @@
     // Before connecting to the surface, we do not get a valid transform hint
     int transformHint;
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ASSERT_EQ(NO_ERROR,
-              surface->connect(NATIVE_WINDOW_API_CPU, new TestProducerListener(igbProducer)));
+              surface->connect(NATIVE_WINDOW_API_CPU, new TestSurfaceListener(igbProducer)));
 
     // After connecting to the surface, we should get the correct hint.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     ANativeWindow_Buffer buffer;
     surface->lock(&buffer, nullptr /* inOutDirtyBounds */);
@@ -1331,13 +1424,13 @@
 
     // The hint does not change and matches the value used when dequeueing the buffer.
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_90, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_90, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     surface->unlockAndPost();
 
     // After queuing the buffer, we get the updated transform hint
     surface->query(NATIVE_WINDOW_TRANSFORM_HINT, &transformHint);
-    ASSERT_EQ(ui::Transform::ROT_0, transformHint);
+    ASSERT_EQ(ui::Transform::ROT_0, static_cast<ui::Transform::RotationFlags>(transformHint));
 
     adapter.waitForCallbacks();
 }
@@ -1573,7 +1666,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1591,7 +1684,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1606,7 +1699,7 @@
     // we should also have gotten the initial values for the next frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
 
@@ -1622,7 +1715,7 @@
     // Check the first frame...
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
     ASSERT_GE(events->latchTime, postedTimeA);
@@ -1636,7 +1729,7 @@
     // ...and the second
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1650,7 +1743,7 @@
     // ...and finally the third!
     events = history.getFrame(3);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(3, events->frameNumber);
+    ASSERT_EQ(3u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeC);
 
@@ -1677,7 +1770,7 @@
     FrameEvents* events = nullptr;
     events = history.getFrame(1);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1692,7 +1785,7 @@
     ASSERT_NE(nullptr, events);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1715,7 +1808,7 @@
     adapter.waitForCallback(3);
 
     // frame number, requestedPresentTime, and postTime should not have changed
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1729,7 +1822,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
@@ -1751,7 +1844,7 @@
 
     // frame number, requestedPresentTime, and postTime should not have changed
     events = history.getFrame(1);
-    ASSERT_EQ(1, events->frameNumber);
+    ASSERT_EQ(1u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeA);
 
@@ -1765,7 +1858,7 @@
     // we should also have gotten values for the presented frame
     events = history.getFrame(2);
     ASSERT_NE(nullptr, events);
-    ASSERT_EQ(2, events->frameNumber);
+    ASSERT_EQ(2u, events->frameNumber);
     ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
     ASSERT_GE(events->postedTime, postedTimeB);
     ASSERT_GE(events->latchTime, postedTimeB);
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index 6880678..3b6a66e 100644
--- a/libs/gui/tests/BufferItemConsumer_test.cpp
+++ b/libs/gui/tests/BufferItemConsumer_test.cpp
@@ -17,10 +17,12 @@
 #define LOG_TAG "BufferItemConsumer_test"
 //#define LOG_NDEBUG 0
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <gui/BufferItemConsumer.h>
 #include <gui/IProducerListener.h>
 #include <gui/Surface.h>
+#include <ui/GraphicBuffer.h>
 
 namespace android {
 
@@ -28,6 +30,7 @@
 static constexpr int kHeight = 100;
 static constexpr int kMaxLockedBuffers = 3;
 static constexpr int kFormat = HAL_PIXEL_FORMAT_RGBA_8888;
+static constexpr int kUsage = GRALLOC_USAGE_SW_READ_RARELY;
 static constexpr int kFrameSleepUs = 30 * 1000;
 
 class BufferItemConsumerTest : public ::testing::Test {
@@ -42,16 +45,26 @@
         BufferItemConsumerTest* mTest;
     };
 
+    struct TrackingProducerListener : public BnProducerListener {
+        TrackingProducerListener(BufferItemConsumerTest* test) : mTest(test) {}
+
+        virtual void onBufferReleased() override {}
+        virtual bool needsReleaseNotify() override { return true; }
+        virtual void onBuffersDiscarded(const std::vector<int32_t>&) override {}
+        virtual void onBufferDetached(int slot) override { mTest->HandleBufferDetached(slot); }
+
+        BufferItemConsumerTest* mTest;
+    };
+
     void SetUp() override {
-        BufferQueue::createBufferQueue(&mProducer, &mConsumer);
-        mBIC =
-            new BufferItemConsumer(mConsumer, kFormat, kMaxLockedBuffers, true);
+        mBIC = new BufferItemConsumer(kUsage, kMaxLockedBuffers, true);
         String8 name("BufferItemConsumer_Under_Test");
         mBIC->setName(name);
         mBFL = new BufferFreedListener(this);
         mBIC->setBufferFreedListener(mBFL);
 
-        sp<IProducerListener> producerListener = new StubProducerListener();
+        sp<IProducerListener> producerListener = new TrackingProducerListener(this);
+        mProducer = mBIC->getSurface()->getIGraphicBufferProducer();
         IGraphicBufferProducer::QueueBufferOutput bufferOutput;
         ASSERT_EQ(NO_ERROR,
                   mProducer->connect(producerListener, NATIVE_WINDOW_API_CPU,
@@ -71,6 +84,13 @@
         ALOGD("HandleBufferFreed, mFreedBufferCount=%d", mFreedBufferCount);
     }
 
+    void HandleBufferDetached(int slot) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        mDetachedBufferSlots.push_back(slot);
+        ALOGD("HandleBufferDetached, slot=%d mDetachedBufferSlots-count=%zu", slot,
+              mDetachedBufferSlots.size());
+    }
+
     void DequeueBuffer(int* outSlot) {
         ASSERT_NE(outSlot, nullptr);
 
@@ -120,6 +140,7 @@
 
     std::mutex mMutex;
     int mFreedBufferCount{0};
+    std::vector<int> mDetachedBufferSlots = {};
 
     sp<BufferItemConsumer> mBIC;
     sp<BufferFreedListener> mBFL;
@@ -203,4 +224,19 @@
     ASSERT_EQ(1, GetFreedBufferCount());
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+// Test that delete BufferItemConsumer triggers onBufferFreed.
+TEST_F(BufferItemConsumerTest, DetachBufferWithBuffer) {
+    int slot;
+    // Let buffer go through the cycle at least once.
+    DequeueBuffer(&slot);
+    QueueBuffer(slot);
+    AcquireBuffer(&slot);
+
+    sp<GraphicBuffer> buffer = mBuffers[slot];
+    EXPECT_EQ(OK, mBIC->detachBuffer(buffer));
+    EXPECT_THAT(mDetachedBufferSlots, testing::ElementsAre(slot));
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 }  // namespace android
diff --git a/libs/gui/tests/BufferQueue_test.cpp b/libs/gui/tests/BufferQueue_test.cpp
index be6c7d6..2e6ffcb 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -28,6 +28,8 @@
 
 #include <ui/GraphicBuffer.h>
 
+#include <android-base/properties.h>
+
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
@@ -47,6 +49,10 @@
 
 using namespace std::chrono_literals;
 
+static bool IsCuttlefish() {
+    return ::android::base::GetProperty("ro.product.board", "") == "cutf";
+}
+
 namespace android {
 using namespace com::android::graphics::libgui;
 
@@ -119,8 +125,7 @@
     }
 
     sp<IServiceManager> serviceManager = defaultServiceManager();
-    sp<IBinder> binderProducer =
-        serviceManager->getService(PRODUCER_NAME);
+    sp<IBinder> binderProducer = serviceManager->waitForService(PRODUCER_NAME);
     mProducer = interface_cast<IGraphicBufferProducer>(binderProducer);
     EXPECT_TRUE(mProducer != nullptr);
     sp<IBinder> binderConsumer =
@@ -1114,7 +1119,7 @@
 
     // Check onBuffersDiscarded is called with correct slots
     auto buffersDiscarded = pl->getDiscardedSlots();
-    ASSERT_EQ(buffersDiscarded.size(), 1);
+    ASSERT_EQ(buffersDiscarded.size(), 1u);
     ASSERT_EQ(buffersDiscarded[0], releasedSlot);
 
     // Check no free buffers in dump
@@ -1239,7 +1244,7 @@
     ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
 
     // Check whether the slot from IProducerListener is same to the detached slot.
-    ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+    ASSERT_EQ(pl->getDetachedSlots().size(), 1u);
     ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
 
     // Dequeue another buffer.
@@ -1425,19 +1430,15 @@
 }
 
 struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
-    BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer)
-          : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
+    BufferItemConsumerSetFrameRateListener() : BufferItemConsumer(GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
 
     MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override));
 };
 
 TEST_F(BufferQueueTest, TestSetFrameRate) {
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-
     sp<BufferItemConsumerSetFrameRateListener> bufferConsumer =
-            sp<BufferItemConsumerSetFrameRateListener>::make(consumer);
+            sp<BufferItemConsumerSetFrameRateListener>::make();
+    sp<IGraphicBufferProducer> producer = bufferConsumer->getSurface()->getIGraphicBufferProducer();
 
     EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1);
     producer->setFrameRate(12.34f, 1, 0);
@@ -1488,14 +1489,10 @@
 
 // See b/270004534
 TEST(BufferQueueThreading, TestProducerDequeueConsumerDestroy) {
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-
     sp<BufferItemConsumer> bufferConsumer =
-            sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+            sp<BufferItemConsumer>::make(GRALLOC_USAGE_SW_READ_OFTEN, 2);
     ASSERT_NE(nullptr, bufferConsumer.get());
-    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<Surface> surface = bufferConsumer->getSurface();
     native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
     native_window_set_buffers_dimensions(surface.get(), 100, 100);
 
@@ -1525,4 +1522,51 @@
     EXPECT_EQ(nullptr, bufferConsumer.get());
 }
 
+TEST_F(BufferQueueTest, TestAdditionalOptions) {
+    sp<BufferItemConsumer> bufferConsumer =
+            sp<BufferItemConsumer>::make(GRALLOC_USAGE_SW_READ_OFTEN, 2);
+    ASSERT_NE(nullptr, bufferConsumer.get());
+    sp<Surface> surface = bufferConsumer->getSurface();
+    native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+    native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+    std::array<AHardwareBufferLongOptions, 1> extras = {{
+            {.name = "android.hardware.graphics.common.Dataspace", ADATASPACE_DISPLAY_P3},
+    }};
+
+    ASSERT_EQ(NO_INIT,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    if (!IsCuttlefish()) {
+        GTEST_SKIP() << "Not cuttlefish";
+    }
+
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK,
+              native_window_set_buffers_additional_options(surface.get(), extras.data(),
+                                                           extras.size()));
+
+    ANativeWindowBuffer* windowBuffer = nullptr;
+    int fence = -1;
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+
+    AHardwareBuffer* buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    ADataSpace dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_DISPLAY_P3, dataSpace);
+
+    ANativeWindow_cancelBuffer(surface.get(), windowBuffer, -1);
+
+    // Check that reconnecting properly clears the options
+    ASSERT_EQ(OK, native_window_api_disconnect(surface.get(), NATIVE_WINDOW_API_CPU));
+    ASSERT_EQ(OK, native_window_api_connect(surface.get(), NATIVE_WINDOW_API_CPU));
+
+    ASSERT_EQ(OK, ANativeWindow_dequeueBuffer(surface.get(), &windowBuffer, &fence));
+    buffer = ANativeWindowBuffer_getHardwareBuffer(windowBuffer);
+    ASSERT_TRUE(buffer);
+    dataSpace = AHardwareBuffer_getDataSpace(buffer);
+    EXPECT_EQ(ADATASPACE_UNKNOWN, dataSpace);
+}
+
 } // namespace android
diff --git a/libs/gui/tests/BufferReleaseChannel_test.cpp b/libs/gui/tests/BufferReleaseChannel_test.cpp
new file mode 100644
index 0000000..11d122b
--- /dev/null
+++ b/libs/gui/tests/BufferReleaseChannel_test.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2024 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 <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+#include <gui/BufferReleaseChannel.h>
+
+using namespace std::string_literals;
+using android::gui::BufferReleaseChannel;
+
+namespace android {
+
+namespace {
+
+// Helper function to check if two file descriptors point to the same file.
+bool is_same_file(int fd1, int fd2) {
+    struct stat stat1;
+    if (fstat(fd1, &stat1) != 0) {
+        return false;
+    }
+    struct stat stat2;
+    if (fstat(fd2, &stat2) != 0) {
+        return false;
+    }
+    return (stat1.st_dev == stat2.st_dev) && (stat1.st_ino == stat2.st_ino);
+}
+
+} // namespace
+
+TEST(BufferReleaseChannelTest, MessageFlattenable) {
+    ReleaseCallbackId releaseCallbackId{1, 2};
+    sp<Fence> releaseFence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
+    uint32_t maxAcquiredBufferCount = 5;
+
+    std::vector<uint8_t> dataBuffer;
+    std::vector<int> fdBuffer;
+
+    // Verify that we can flatten a message
+    {
+        BufferReleaseChannel::Message message{releaseCallbackId, releaseFence,
+                                              maxAcquiredBufferCount};
+
+        dataBuffer.resize(message.getFlattenedSize());
+        void* dataPtr = dataBuffer.data();
+        size_t dataSize = dataBuffer.size();
+
+        fdBuffer.resize(message.getFdCount());
+        int* fdPtr = fdBuffer.data();
+        size_t fdSize = fdBuffer.size();
+
+        ASSERT_EQ(OK, message.flatten(dataPtr, dataSize, fdPtr, fdSize));
+
+        // Fence's unique_fd uses fdsan to check ownership of the file descriptor. Normally the file
+        // descriptor is passed through the Unix socket and duplicated (and sent to another process)
+        // so there's no problem with duplicate file descriptor ownership. For this unit test, we
+        // need to set up a duplicate file descriptor to avoid crashing due to duplicate ownership.
+        ASSERT_EQ(releaseFence->get(), fdBuffer[0]);
+        fdBuffer[0] = message.releaseFence->dup();
+    }
+
+    // Verify that we can unflatten a message
+    {
+        BufferReleaseChannel::Message message;
+
+        const void* dataPtr = dataBuffer.data();
+        size_t dataSize = dataBuffer.size();
+
+        const int* fdPtr = fdBuffer.data();
+        size_t fdSize = fdBuffer.size();
+
+        ASSERT_EQ(OK, message.unflatten(dataPtr, dataSize, fdPtr, fdSize));
+        ASSERT_EQ(releaseCallbackId, message.releaseCallbackId);
+        ASSERT_TRUE(is_same_file(releaseFence->get(), message.releaseFence->get()));
+        ASSERT_EQ(maxAcquiredBufferCount, message.maxAcquiredBufferCount);
+    }
+}
+
+// Verify that the BufferReleaseChannel consume returns WOULD_BLOCK when there's no message
+// available.
+TEST(BufferReleaseChannelTest, ConsumerEndpointIsNonBlocking) {
+    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
+    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
+    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
+
+    ReleaseCallbackId releaseCallbackId;
+    sp<Fence> releaseFence;
+    uint32_t maxAcquiredBufferCount;
+    ASSERT_EQ(WOULD_BLOCK,
+              consumer->readReleaseFence(releaseCallbackId, releaseFence, maxAcquiredBufferCount));
+}
+
+// Verify that we can write a message to the BufferReleaseChannel producer and read that message
+// using the BufferReleaseChannel consumer.
+TEST(BufferReleaseChannelTest, ProduceAndConsume) {
+    std::unique_ptr<BufferReleaseChannel::ConsumerEndpoint> consumer;
+    std::shared_ptr<BufferReleaseChannel::ProducerEndpoint> producer;
+    ASSERT_EQ(OK, BufferReleaseChannel::open("test-channel"s, consumer, producer));
+
+    sp<Fence> fence = sp<Fence>::make(memfd_create("fake-fence-fd", 0));
+
+    for (uint64_t i = 0; i < 64; i++) {
+        ReleaseCallbackId producerId{i, i + 1};
+        uint32_t maxAcquiredBufferCount = i + 2;
+        ASSERT_EQ(OK, producer->writeReleaseFence(producerId, fence, maxAcquiredBufferCount));
+    }
+
+    for (uint64_t i = 0; i < 64; i++) {
+        ReleaseCallbackId expectedId{i, i + 1};
+        uint32_t expectedMaxAcquiredBufferCount = i + 2;
+
+        ReleaseCallbackId consumerId;
+        sp<Fence> consumerFence;
+        uint32_t maxAcquiredBufferCount;
+        ASSERT_EQ(OK,
+                  consumer->readReleaseFence(consumerId, consumerFence, maxAcquiredBufferCount));
+
+        ASSERT_EQ(expectedId, consumerId);
+        ASSERT_TRUE(is_same_file(fence->get(), consumerFence->get()));
+        ASSERT_EQ(expectedMaxAcquiredBufferCount, maxAcquiredBufferCount);
+    }
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/Choreographer_test.cpp b/libs/gui/tests/Choreographer_test.cpp
new file mode 100644
index 0000000..8db48d2
--- /dev/null
+++ b/libs/gui/tests/Choreographer_test.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Choreographer_test"
+
+#include <android-base/stringprintf.h>
+#include <android/choreographer.h>
+#include <gtest/gtest.h>
+#include <gui/Choreographer.h>
+#include <utils/Looper.h>
+#include <chrono>
+#include <future>
+#include <string>
+
+namespace android {
+class ChoreographerTest : public ::testing::Test {};
+
+struct VsyncCallback {
+    std::atomic<bool> completePromise{false};
+    std::chrono::nanoseconds frameTime{0LL};
+    std::chrono::nanoseconds receivedCallbackTime{0LL};
+
+    void onVsyncCallback(const AChoreographerFrameCallbackData* callbackData) {
+        frameTime = std::chrono::nanoseconds{
+                AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData)};
+        receivedCallbackTime = std::chrono::nanoseconds{systemTime(SYSTEM_TIME_MONOTONIC)};
+        completePromise.store(true);
+    }
+
+    bool callbackReceived() { return completePromise.load(); }
+};
+
+static void vsyncCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
+    VsyncCallback* cb = static_cast<VsyncCallback*>(data);
+    cb->onVsyncCallback(callbackData);
+}
+
+TEST_F(ChoreographerTest, InputCallbackBeforeAnimation) {
+    sp<Looper> looper = Looper::prepare(0);
+    Choreographer* choreographer = Choreographer::getForThread();
+    VsyncCallback animationCb;
+    choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &animationCb, 0,
+                                            CALLBACK_ANIMATION);
+    VsyncCallback inputCb;
+    choreographer->postFrameCallbackDelayed(nullptr, nullptr, vsyncCallback, &inputCb, 0,
+                                            CALLBACK_INPUT);
+    auto startTime = std::chrono::system_clock::now();
+    do {
+        static constexpr int32_t timeoutMs = 1000;
+        int pollResult = looper->pollOnce(timeoutMs);
+        ASSERT_TRUE((pollResult != Looper::POLL_TIMEOUT) && (pollResult != Looper::POLL_ERROR))
+                << "Failed to poll looper. Poll result = " << pollResult;
+        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(
+                std::chrono::system_clock::now() - startTime);
+        ASSERT_LE(elapsedMs.count(), timeoutMs)
+                << "Timed out waiting for callbacks. inputCb=" << inputCb.callbackReceived()
+                << " animationCb=" << animationCb.callbackReceived();
+    } while (!(inputCb.callbackReceived() && animationCb.callbackReceived()));
+
+    ASSERT_EQ(inputCb.frameTime, animationCb.frameTime)
+            << android::base::StringPrintf("input and animation callback frame times don't match. "
+                                           "inputFrameTime=%lld  animationFrameTime=%lld",
+                                           inputCb.frameTime.count(),
+                                           animationCb.frameTime.count());
+
+    ASSERT_LT(inputCb.receivedCallbackTime, animationCb.receivedCallbackTime)
+            << android::base::StringPrintf("input callback was not called first. "
+                                           "inputCallbackTime=%lld  animationCallbackTime=%lld",
+                                           inputCb.frameTime.count(),
+                                           animationCb.frameTime.count());
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index d80bd9c..f4239cb 100644
--- a/libs/gui/tests/CpuConsumer_test.cpp
+++ b/libs/gui/tests/CpuConsumer_test.cpp
@@ -66,13 +66,10 @@
                 test_info->name(),
                 params.width, params.height,
                 params.maxLockedBuffers, params.format);
-        sp<IGraphicBufferProducer> producer;
-        sp<IGraphicBufferConsumer> consumer;
-        BufferQueue::createBufferQueue(&producer, &consumer);
-        mCC = new CpuConsumer(consumer, params.maxLockedBuffers);
+        mCC = new CpuConsumer(params.maxLockedBuffers);
         String8 name("CpuConsumer_Under_Test");
         mCC->setName(name);
-        mSTC = new Surface(producer);
+        mSTC = mCC->getSurface();
         mANW = mSTC;
     }
 
diff --git a/libs/gui/tests/DisplayInfo_test.cpp b/libs/gui/tests/DisplayInfo_test.cpp
index df3329c..4df76b1 100644
--- a/libs/gui/tests/DisplayInfo_test.cpp
+++ b/libs/gui/tests/DisplayInfo_test.cpp
@@ -28,7 +28,7 @@
 
 TEST(DisplayInfo, Parcelling) {
     DisplayInfo info;
-    info.displayId = 42;
+    info.displayId = ui::LogicalDisplayId{42};
     info.logicalWidth = 99;
     info.logicalHeight = 78;
     info.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp
index 0a2750a..bffb3f0 100644
--- a/libs/gui/tests/DisplayedContentSampling_test.cpp
+++ b/libs/gui/tests/DisplayedContentSampling_test.cpp
@@ -116,10 +116,10 @@
     EXPECT_EQ(OK, status);
     if (stats.numFrames <= 0) return;
 
-    if (componentMask & (0x1 << 0)) EXPECT_NE(0, stats.component_0_sample.size());
-    if (componentMask & (0x1 << 1)) EXPECT_NE(0, stats.component_1_sample.size());
-    if (componentMask & (0x1 << 2)) EXPECT_NE(0, stats.component_2_sample.size());
-    if (componentMask & (0x1 << 3)) EXPECT_NE(0, stats.component_3_sample.size());
+    if (componentMask & (0x1 << 0)) EXPECT_NE(0u, stats.component_0_sample.size());
+    if (componentMask & (0x1 << 1)) EXPECT_NE(0u, stats.component_1_sample.size());
+    if (componentMask & (0x1 << 2)) EXPECT_NE(0u, stats.component_2_sample.size());
+    if (componentMask & (0x1 << 3)) EXPECT_NE(0u, stats.component_3_sample.size());
 }
 
 } // namespace android
diff --git a/libs/gui/tests/EndToEndNativeInputTest.cpp b/libs/gui/tests/EndToEndNativeInputTest.cpp
index a9d6e8d..7d0b512 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
 
 #include <memory>
 
+#include <android-base/thread_annotations.h>
 #include <android/gui/BnWindowInfosReportedListener.h>
 #include <android/keycodes.h>
 #include <android/native_window.h>
@@ -41,6 +42,7 @@
 #include <android/os/IInputFlinger.h>
 #include <gui/WindowInfo.h>
 #include <input/Input.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 #include <ui/DisplayMode.h>
@@ -58,7 +60,13 @@
 using android::gui::TouchOcclusionMode;
 using android::gui::WindowInfo;
 
-namespace android::test {
+namespace android {
+namespace {
+ui::LogicalDisplayId toDisplayId(ui::LayerStack layerStack) {
+    return ui::LogicalDisplayId{static_cast<int32_t>(layerStack.id)};
+}
+} // namespace
+namespace test {
 
 using Transaction = SurfaceComposerClient::Transaction;
 
@@ -66,7 +74,9 @@
     sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
     if (input == nullptr) {
         ALOGE("Failed to link to input service");
-    } else { ALOGE("Linked to input"); }
+    } else {
+        ALOGE("Linked to input");
+    }
     return interface_cast<IInputFlinger>(input);
 }
 
@@ -77,26 +87,27 @@
 class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
 public:
     binder::Status onWindowInfosReported() override {
-        std::lock_guard<std::mutex> lock{mMutex};
+        std::scoped_lock lock{mLock};
         mWindowInfosReported = true;
         mConditionVariable.notify_one();
         return binder::Status::ok();
     }
 
     void wait() {
-        std::unique_lock<std::mutex> lock{mMutex};
-        mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+        std::unique_lock lock{mLock};
+        android::base::ScopedLockAssertion assumeLocked(mLock);
+        mConditionVariable.wait(lock, [&]() REQUIRES(mLock) { return mWindowInfosReported; });
     }
 
 private:
-    std::mutex mMutex;
+    std::mutex mLock;
     std::condition_variable mConditionVariable;
-    bool mWindowInfosReported{false};
+    bool mWindowInfosReported GUARDED_BY(mLock){false};
 };
 
 class InputSurface {
 public:
-    InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
+    InputSurface(const sp<SurfaceControl>& sc, int width, int height, bool noInputChannel = false) {
         mSurfaceControl = sc;
 
         mInputFlinger = getInputFlinger();
@@ -127,7 +138,7 @@
         mInputInfo.applicationInfo = aInfo;
     }
 
-    static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient> &scc,
+    static std::unique_ptr<InputSurface> makeColorInputSurface(const sp<SurfaceComposerClient>& scc,
                                                                int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Surface"), 0 /* bufHeight */, 0 /* bufWidth */,
@@ -137,7 +148,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeBufferInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Buffer Surface"), width, height,
                                    PIXEL_FORMAT_RGBA_8888, 0 /* flags */);
@@ -145,7 +156,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeContainerInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Container Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -154,7 +165,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeContainerInputSurfaceNoInputChannel(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Container Surface"), 100 /* height */,
                                    100 /* width */, PIXEL_FORMAT_RGBA_8888,
@@ -164,7 +175,7 @@
     }
 
     static std::unique_ptr<InputSurface> makeCursorInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> surfaceControl =
                 scc->createSurface(String8("Test Cursor Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -175,7 +186,7 @@
     InputEvent* consumeEvent(std::chrono::milliseconds timeout = 3000ms) {
         mClientChannel->waitForMessage(timeout);
 
-        InputEvent *ev;
+        InputEvent* ev;
         uint32_t seqId;
         status_t consumed = mInputConsumer->consume(&mInputEventFactory, true, -1, &seqId, &ev);
         if (consumed != OK) {
@@ -187,14 +198,14 @@
     }
 
     void assertFocusChange(bool hasFocus) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::FOCUS, ev->getType());
-        FocusEvent *focusEvent = static_cast<FocusEvent *>(ev);
+        FocusEvent* focusEvent = static_cast<FocusEvent*>(ev);
         EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
     }
 
-    void expectTap(int x, int y) {
+    void expectTap(float x, float y) {
         InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
@@ -213,10 +224,10 @@
     }
 
     void expectTapWithFlag(int x, int y, int32_t flags) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        MotionEvent* mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
         EXPECT_EQ(x, mev->getX(0));
         EXPECT_EQ(y, mev->getY(0));
@@ -225,18 +236,18 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        mev = static_cast<MotionEvent *>(ev);
+        mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(flags, mev->getFlags() & flags);
     }
 
     void expectTapInDisplayCoordinates(int displayX, int displayY) {
-        InputEvent *ev = consumeEvent();
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        MotionEvent *mev = static_cast<MotionEvent *>(ev);
+        MotionEvent* mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
-        const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
+        const PointerCoords& coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
         EXPECT_EQ(displayX, coords.getX());
         EXPECT_EQ(displayY, coords.getY());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -244,16 +255,16 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::MOTION, ev->getType());
-        mev = static_cast<MotionEvent *>(ev);
+        mev = static_cast<MotionEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
         EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
     }
 
-    void expectKey(uint32_t keycode) {
-        InputEvent *ev = consumeEvent();
+    void expectKey(int32_t keycode) {
+        InputEvent* ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::KEY, ev->getType());
-        KeyEvent *keyEvent = static_cast<KeyEvent *>(ev);
+        KeyEvent* keyEvent = static_cast<KeyEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
         EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS);
@@ -261,12 +272,17 @@
         ev = consumeEvent();
         ASSERT_NE(ev, nullptr);
         ASSERT_EQ(InputEventType::KEY, ev->getType());
-        keyEvent = static_cast<KeyEvent *>(ev);
+        keyEvent = static_cast<KeyEvent*>(ev);
         EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction());
         EXPECT_EQ(keycode, keyEvent->getKeyCode());
         EXPECT_EQ(0, keyEvent->getFlags() & VERIFIED_KEY_EVENT_FLAGS);
     }
 
+    void assertNoEvent() {
+        InputEvent* event = consumeEvent(/*timeout=*/100ms);
+        ASSERT_EQ(event, nullptr) << "Expected no event, but got " << *event;
+    }
+
     virtual ~InputSurface() {
         if (mClientChannel) {
             mInputFlinger->removeInputChannel(mClientChannel->getConnectionToken());
@@ -274,7 +290,7 @@
     }
 
     virtual void doTransaction(
-            std::function<void(SurfaceComposerClient::Transaction &, const sp<SurfaceControl> &)>
+            std::function<void(SurfaceComposerClient::Transaction&, const sp<SurfaceControl>&)>
                     transactionBody) {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mSurfaceControl);
@@ -295,13 +311,13 @@
         reportedListener->wait();
     }
 
-    void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
+    void requestFocus(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT) {
         SurfaceComposerClient::Transaction t;
         FocusRequest request;
         request.token = mInputInfo.token;
         request.windowName = mInputInfo.name;
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-        request.displayId = displayId;
+        request.displayId = displayId.val();
         t.setFocusedWindow(request);
         t.apply(true);
     }
@@ -319,7 +335,7 @@
 
 class BlastInputSurface : public InputSurface {
 public:
-    BlastInputSurface(const sp<SurfaceControl> &sc, const sp<SurfaceControl> &parentSc, int width,
+    BlastInputSurface(const sp<SurfaceControl>& sc, const sp<SurfaceControl>& parentSc, int width,
                       int height)
           : InputSurface(sc, width, height) {
         mParentSurfaceControl = parentSc;
@@ -328,7 +344,7 @@
     ~BlastInputSurface() = default;
 
     static std::unique_ptr<BlastInputSurface> makeBlastInputSurface(
-            const sp<SurfaceComposerClient> &scc, int width, int height) {
+            const sp<SurfaceComposerClient>& scc, int width, int height) {
         sp<SurfaceControl> parentSc =
                 scc->createSurface(String8("Test Parent Surface"), 0 /* bufHeight */,
                                    0 /* bufWidth */, PIXEL_FORMAT_RGBA_8888,
@@ -343,7 +359,7 @@
     }
 
     void doTransaction(
-            std::function<void(SurfaceComposerClient::Transaction &, const sp<SurfaceControl> &)>
+            std::function<void(SurfaceComposerClient::Transaction&, const sp<SurfaceControl>&)>
                     transactionBody) override {
         SurfaceComposerClient::Transaction t;
         transactionBody(t, mParentSurfaceControl);
@@ -370,9 +386,7 @@
 
 class InputSurfacesTest : public ::testing::Test {
 public:
-    InputSurfacesTest() {
-        ProcessState::self()->startThreadPool();
-    }
+    InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
 
     void SetUp() {
         mComposerClient = new SurfaceComposerClient;
@@ -392,15 +406,13 @@
         mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
     }
 
-    void TearDown() {
-        mComposerClient->dispose();
-    }
+    void TearDown() { mComposerClient->dispose(); }
 
     std::unique_ptr<InputSurface> makeSurface(int width, int height) {
         return InputSurface::makeColorInputSurface(mComposerClient, width, height);
     }
 
-    void postBuffer(const sp<SurfaceControl> &layer, int32_t w, int32_t h) {
+    void postBuffer(const sp<SurfaceControl>& layer, int32_t w, int32_t h) {
         int64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
                 BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
         sp<GraphicBuffer> buffer =
@@ -413,31 +425,31 @@
     int32_t mBufferPostDelay;
 };
 
-void injectTapOnDisplay(int x, int y, int displayId) {
+void injectTapOnDisplay(int x, int y, ui::LogicalDisplayId displayId) {
     char *buf1, *buf2, *bufDisplayId;
     asprintf(&buf1, "%d", x);
     asprintf(&buf2, "%d", y);
-    asprintf(&bufDisplayId, "%d", displayId);
+    asprintf(&bufDisplayId, "%d", displayId.val());
     if (fork() == 0) {
         execlp("input", "input", "-d", bufDisplayId, "tap", buf1, buf2, NULL);
     }
 }
 
 void injectTap(int x, int y) {
-    injectTapOnDisplay(x, y, ADISPLAY_ID_DEFAULT);
+    injectTapOnDisplay(x, y, ui::LogicalDisplayId::DEFAULT);
 }
 
-void injectKeyOnDisplay(uint32_t keycode, int displayId) {
+void injectKeyOnDisplay(uint32_t keycode, ui::LogicalDisplayId displayId) {
     char *buf1, *bufDisplayId;
     asprintf(&buf1, "%d", keycode);
-    asprintf(&bufDisplayId, "%d", displayId);
+    asprintf(&bufDisplayId, "%d", displayId.val());
     if (fork() == 0) {
         execlp("input", "input", "-d", bufDisplayId, "keyevent", buf1, NULL);
     }
 }
 
 void injectKey(uint32_t keycode) {
-    injectKeyOnDisplay(keycode, ADISPLAY_ID_NONE);
+    injectKeyOnDisplay(keycode, ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputSurfacesTest, can_receive_input) {
@@ -468,12 +480,8 @@
     injectTap(101, 101);
     surface->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.setPosition(sc, 100, 100);
-    });
-    surface->doTransaction([](auto &t, auto &sc) {
-         t.setPosition(sc, 200, 200);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 100, 100); });
+    surface->doTransaction([](auto& t, auto& sc) { t.setPosition(sc, 200, 200); });
 
     injectTap(101, 101);
     surface2->expectTap(1, 1);
@@ -489,23 +497,17 @@
     surface->showAt(10, 10);
     surface2->showAt(10, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
-         t.setLayer(sc, LAYER_BASE + 1);
-    });
+    surface->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); });
 
     injectTap(11, 11);
     surface->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.setLayer(sc, LAYER_BASE + 1);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.setLayer(sc, LAYER_BASE + 1); });
 
     injectTap(11, 11);
     surface2->expectTap(1, 1);
 
-    surface2->doTransaction([](auto &t, auto &sc) {
-         t.hide(sc);
-    });
+    surface2->doTransaction([](auto& t, auto& sc) { t.hide(sc); });
 
     injectTap(11, 11);
     surface->expectTap(1, 1);
@@ -554,7 +556,7 @@
     childSurface->mInputInfo.surfaceInset = 10;
     childSurface->showAt(100, 100);
 
-    childSurface->doTransaction([&](auto &t, auto &sc) {
+    childSurface->doTransaction([&](auto& t, auto& sc) {
         t.setPosition(sc, -5, -5);
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -575,7 +577,7 @@
     fgSurface->mInputInfo.surfaceInset = 5;
     fgSurface->showAt(100, 100);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 4.0); });
 
     // expect = touch / scale - inset
     injectTap(112, 124);
@@ -594,7 +596,7 @@
     fgSurface->mInputInfo.surfaceInset = INT32_MAX;
     fgSurface->showAt(100, 100);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // expect no crash for overflow, and inset size to be clamped to surface size
     injectTap(112, 124);
@@ -643,7 +645,7 @@
     fgSurface->mInputInfo.touchableRegion.orSelf(Rect{INT32_MIN, INT32_MIN, INT32_MAX, INT32_MAX});
     fgSurface->showAt(0, 0);
 
-    fgSurface->doTransaction([&](auto &t, auto &sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
+    fgSurface->doTransaction([&](auto& t, auto& sc) { t.setMatrix(sc, 2.0, 0, 0, 2.0); });
 
     // Expect no crash for overflow.
     injectTap(12, 24);
@@ -653,7 +655,7 @@
 // Ensure we ignore transparent region when getting screen bounds when positioning input frame.
 TEST_F(InputSurfacesTest, input_ignores_transparent_region) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         Region transparentRegion(Rect(0, 0, 10, 10));
         t.setTransparentRegionHint(sc, transparentRegion);
     });
@@ -694,7 +696,7 @@
     injectTap(11, 11);
     bufferSurface->expectTap(1, 1);
 
-    bufferSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); });
+    bufferSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); });
 
     injectTap(11, 11);
     bgSurface->expectTap(1, 1);
@@ -710,7 +712,7 @@
     injectTap(11, 11);
     fgSurface->expectTap(1, 1);
 
-    fgSurface->doTransaction([](auto &t, auto &sc) { t.setAlpha(sc, 0.0); });
+    fgSurface->doTransaction([](auto& t, auto& sc) { t.setAlpha(sc, 0.0); });
 
     injectTap(11, 11);
     fgSurface->expectTap(1, 1);
@@ -727,7 +729,7 @@
     injectTap(11, 11);
     containerSurface->expectTap(1, 1);
 
-    containerSurface->doTransaction([](auto &t, auto &sc) { t.hide(sc); });
+    containerSurface->doTransaction([](auto& t, auto& sc) { t.hide(sc); });
 
     injectTap(11, 11);
     bgSurface->expectTap(1, 1);
@@ -767,19 +769,19 @@
 TEST_F(InputSurfacesTest, rotate_surface) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(10, 10);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 1, -1, 0); // 90 degrees
     });
     injectTap(8, 11);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -1, 0, 0, -1); // 180 degrees
     });
     injectTap(9, 8);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -1, 1, 0); // 270 degrees
     });
     injectTap(12, 9);
@@ -789,19 +791,19 @@
 TEST_F(InputSurfacesTest, rotate_surface_with_scale) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(10, 10);
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees
     });
     injectTap(2, 12);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees
     });
     injectTap(8, 2);
     surface->expectTap(1, 2);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees
     });
     injectTap(18, 8);
@@ -813,19 +815,19 @@
     surface->mInputInfo.surfaceInset = 5;
     surface->showAt(100, 100);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, 2, -4, 0); // 90 degrees
     });
     injectTap(40, 120);
     surface->expectTap(5, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, -2, 0, 0, -4); // 180 degrees
     });
     injectTap(80, 40);
     surface->expectTap(5, 10);
 
-    surface->doTransaction([](auto &t, auto &sc) {
+    surface->doTransaction([](auto& t, auto& sc) {
         t.setMatrix(sc, 0, -2, 4, 0); // 270 degrees
     });
     injectTap(160, 80);
@@ -866,7 +868,7 @@
     nonTouchableSurface->showAt(0, 0);
     parentSurface->showAt(100, 100);
 
-    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+    nonTouchableSurface->doTransaction([&](auto& t, auto& sc) {
         t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -890,7 +892,7 @@
     nonTouchableSurface->showAt(0, 0);
     parentSurface->showAt(50, 50);
 
-    nonTouchableSurface->doTransaction([&](auto &t, auto &sc) {
+    nonTouchableSurface->doTransaction([&](auto& t, auto& sc) {
         t.setCrop(parentSurface->mSurfaceControl, Rect(0, 0, 50, 50));
         t.reparent(sc, parentSurface->mSurfaceControl);
     });
@@ -932,13 +934,11 @@
 TEST_F(InputSurfacesTest, strict_unobscured_input_unobscured_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    surface->expectTap(1, 1);
 
     surface->requestFocus();
     surface->assertFocusChange(true);
@@ -948,16 +948,14 @@
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.setMatrix(sc, 2.0, 0, 0, 2.0);
     });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    surface->expectTap(.5, .5);
 
     surface->requestFocus();
     surface->assertFocusChange(true);
@@ -969,26 +967,26 @@
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->mInputInfo.ownerUid = gui::Uid{11111};
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
     obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
     obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
     obscuringSurface->showAt(100, 100);
     injectTap(101, 101);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->mInputInfo.ownerUid = gui::Uid{11111};
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
     surface->showAt(100, 100);
     std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
     obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
@@ -997,12 +995,12 @@
 
     injectTap(101, 101);
 
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_alpha_window) {
@@ -1011,7 +1009,7 @@
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->showAt(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.reparent(sc, parentSurface->mSurfaceControl);
         t.setAlpha(parentSurface->mSurfaceControl, 0.9f);
@@ -1019,12 +1017,12 @@
 
     injectTap(101, 101);
 
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, strict_unobscured_input_cropped_window) {
@@ -1032,7 +1030,7 @@
     parentSurface->showAt(0, 0, Rect(0, 0, 300, 300));
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setDropInputMode(sc, gui::DropInputMode::OBSCURED);
         t.reparent(sc, parentSurface->mSurfaceControl);
         t.setCrop(parentSurface->mSurfaceControl, Rect(10, 10, 100, 100));
@@ -1041,12 +1039,12 @@
 
     injectTap(111, 111);
 
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, ignore_touch_region_with_zero_sized_blast) {
@@ -1066,17 +1064,16 @@
 TEST_F(InputSurfacesTest, drop_input_policy) {
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
     surface->doTransaction(
-            [&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); });
+            [&](auto& t, auto& sc) { t.setDropInputMode(sc, gui::DropInputMode::ALL); });
     surface->showAt(100, 100);
 
     injectTap(101, 101);
-
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
     surface->requestFocus();
     surface->assertFocusChange(true);
     injectKey(AKEYCODE_V);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, layer_with_valid_crop_can_be_focused) {
@@ -1099,7 +1096,7 @@
     std::unique_ptr<InputSurface> containerSurface =
             InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
     containerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+            [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
     containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
     containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
     parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
@@ -1111,7 +1108,7 @@
 
     // Does not receive events outside its crop
     injectTap(26, 26);
-    EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 /**
@@ -1124,7 +1121,7 @@
     std::unique_ptr<InputSurface> containerSurface =
             InputSurface::makeContainerInputSurface(mComposerClient, 100, 100);
     containerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
+            [&](auto& t, auto& sc) { t.reparent(sc, parentContainer->mSurfaceControl); });
     containerSurface->mInputInfo.replaceTouchableRegionWithCrop = true;
     containerSurface->mInputInfo.touchableRegionCropHandle = nullptr;
     parentContainer->showAt(10, 10, Rect(0, 0, 20, 20));
@@ -1136,7 +1133,7 @@
 
     // Does not receive events outside parent bounds
     injectTap(31, 31);
-    EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 /**
@@ -1162,7 +1159,7 @@
     // Does not receive events outside crop layer bounds
     injectTap(21, 21);
     injectTap(71, 71);
-    EXPECT_EQ(containerSurface->consumeEvent(/*timeout=*/100ms), nullptr);
+    containerSurface->assertNoEvent();
 }
 
 TEST_F(InputSurfacesTest, child_container_with_no_input_channel_blocks_parent) {
@@ -1176,19 +1173,19 @@
             InputSurface::makeContainerInputSurfaceNoInputChannel(mComposerClient, 100, 100);
     childContainerSurface->showAt(0, 0);
     childContainerSurface->doTransaction(
-            [&](auto &t, auto &sc) { t.reparent(sc, parent->mSurfaceControl); });
+            [&](auto& t, auto& sc) { t.reparent(sc, parent->mSurfaceControl); });
     injectTap(101, 101);
 
-    EXPECT_EQ(parent->consumeEvent(/*timeout=*/100ms), nullptr);
+    parent->assertNoEvent();
 }
 
 class MultiDisplayTests : public InputSurfacesTest {
 public:
     MultiDisplayTests() : InputSurfacesTest() { ProcessState::self()->startThreadPool(); }
+
     void TearDown() override {
-        for (auto &token : mVirtualDisplays) {
-            SurfaceComposerClient::destroyDisplay(token);
-        }
+        std::for_each(mVirtualDisplays.begin(), mVirtualDisplays.end(),
+                      SurfaceComposerClient::destroyVirtualDisplay);
         InputSurfacesTest::TearDown();
     }
 
@@ -1203,7 +1200,7 @@
 
         std::string name = "VirtualDisplay";
         name += std::to_string(mVirtualDisplays.size());
-        sp<IBinder> token = SurfaceComposerClient::createDisplay(String8(name.c_str()), isSecure);
+        sp<IBinder> token = SurfaceComposerClient::createVirtualDisplay(name, isSecure);
         SurfaceComposerClient::Transaction t;
         t.setDisplaySurface(token, producer);
         t.setDisplayFlags(token, receivesInput ? 0x01 /* DisplayDevice::eReceivesInput */ : 0);
@@ -1223,18 +1220,18 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     // Do not create a display associated with the LayerStack.
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
     // Touches should be dropped if the layer is on an invalid display.
-    injectTapOnDisplay(101, 101, layerStack.id);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
+    surface->assertNoEvent();
 
     // However, we still let the window be focused and receive keys.
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
 
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
     surface->expectKey(AKEYCODE_V);
 }
 
@@ -1242,15 +1239,15 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) { t.setLayerStack(sc, layerStack); });
+    surface->doTransaction([&](auto& t, auto& sc) { t.setLayerStack(sc, layerStack); });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
     surface->expectTap(1, 1);
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
     surface->expectKey(AKEYCODE_V);
 }
 
@@ -1258,20 +1255,20 @@
     ui::LayerStack layerStack = ui::LayerStack::fromValue(42);
     createDisplay(1000, 1000, false /*isSecure*/, layerStack);
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
         t.setLayerStack(sc, layerStack);
     });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
 
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    surface->assertNoEvent();
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
-    EXPECT_EQ(surface->consumeEvent(/*timeout=*/100ms), nullptr);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
+    surface->assertNoEvent();
 }
 
 TEST_F(MultiDisplayTests, dont_drop_input_for_secure_layer_on_secure_display) {
@@ -1284,21 +1281,21 @@
     seteuid(AID_ROOT);
 
     std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
-    surface->doTransaction([&](auto &t, auto &sc) {
+    surface->doTransaction([&](auto& t, auto& sc) {
         t.setFlags(sc, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
         t.setLayerStack(sc, layerStack);
     });
     surface->showAt(100, 100);
 
-    injectTapOnDisplay(101, 101, layerStack.id);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
-    EXPECT_NE(surface->consumeEvent(), nullptr);
+    injectTapOnDisplay(101, 101, toDisplayId(layerStack));
+    surface->expectTap(1, 1);
 
-    surface->requestFocus(layerStack.id);
+    surface->requestFocus(toDisplayId(layerStack));
     surface->assertFocusChange(true);
-    injectKeyOnDisplay(AKEYCODE_V, layerStack.id);
+    injectKeyOnDisplay(AKEYCODE_V, toDisplayId(layerStack));
 
     surface->expectKey(AKEYCODE_V);
 }
 
-} // namespace android::test
+} // namespace test
+} // namespace android
diff --git a/libs/gui/tests/LibGuiMain.cpp b/libs/gui/tests/LibGuiMain.cpp
index 10f7207..7c7c2cc 100644
--- a/libs/gui/tests/LibGuiMain.cpp
+++ b/libs/gui/tests/LibGuiMain.cpp
@@ -14,8 +14,15 @@
  * limitations under the License.
  */
 
-#include "gtest/gtest.h"
-#include "log/log.h"
+#include <android-base/unique_fd.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include "testserver/TestServer.h"
+#include "testserver/TestServerClient.h"
+#include "testserver/TestServerHost.h"
+
+using namespace android;
 
 namespace {
 
@@ -32,7 +39,34 @@
 } // namespace
 
 int main(int argc, char** argv) {
+    // There are three modes that we can run in to support the libgui TestServer:
+    //
+    // - libgui_test : normal mode, runs tests and fork/execs the testserver host process
+    // - libgui_test --test-server-host $recvPipeFd $sendPipeFd : TestServerHost mode, listens on
+    //   $recvPipeFd for commands and sends responses over $sendPipeFd
+    // - libgui_test --test-server $name : TestServer mode, starts a ITestService binder service
+    //   under $name
+    for (int i = 1; i < argc; i++) {
+        std::string arg = argv[i];
+        if (arg == "--test-server-host") {
+            LOG_ALWAYS_FATAL_IF(argc < (i + 2), "--test-server-host requires two pipe fds");
+            // Note that the send/recv are from our perspective.
+            base::unique_fd recvPipeFd = base::unique_fd(atoi(argv[i + 1]));
+            base::unique_fd sendPipeFd = base::unique_fd(atoi(argv[i + 2]));
+            return TestServerHostMain(argv[0], std::move(sendPipeFd), std::move(recvPipeFd));
+        }
+        if (arg == "--test-server") {
+            LOG_ALWAYS_FATAL_IF(argc < (i + 1), "--test-server requires a name");
+            return TestServerMain(argv[i + 1]);
+        }
+    }
     testing::InitGoogleTest(&argc, argv);
     testing::UnitTest::GetInstance()->listeners().Append(new TestCaseLogger());
+
+    // This has to be run *before* any test initialization, because it fork/execs a TestServerHost,
+    // which will later create new binder service. You can't do that in a forked thread after you've
+    // initialized any binder stuff, which some tests do.
+    TestServerClient::InitializeOrDie(argv[0]);
+
     return RUN_ALL_TESTS();
 }
\ No newline at end of file
diff --git a/libs/gui/tests/MultiTextureConsumer_test.cpp b/libs/gui/tests/MultiTextureConsumer_test.cpp
index 7d3d4aa..2428bb3 100644
--- a/libs/gui/tests/MultiTextureConsumer_test.cpp
+++ b/libs/gui/tests/MultiTextureConsumer_test.cpp
@@ -34,12 +34,8 @@
 
     virtual void SetUp() {
         GLTest::SetUp();
-        sp<IGraphicBufferProducer> producer;
-        sp<IGraphicBufferConsumer> consumer;
-        BufferQueue::createBufferQueue(&producer, &consumer);
-        mGlConsumer = new GLConsumer(consumer, TEX_ID,
-                GLConsumer::TEXTURE_EXTERNAL, true, false);
-        mSurface = new Surface(producer);
+        mGlConsumer = new GLConsumer(TEX_ID, GLConsumer::TEXTURE_EXTERNAL, true, false);
+        mSurface = mGlConsumer->getSurface();
         mANW = mSurface.get();
 
     }
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index b18b544..a0d8c53 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -19,7 +19,7 @@
 
 #include <android/gui/BnRegionSamplingListener.h>
 #include <binder/ProcessState.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/DisplayEventReceiver.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
@@ -239,8 +239,9 @@
     float const luma_green = 0.7152;
     uint32_t const rgba_blue = 0xFFFF0000;
     float const luma_blue = 0.0722;
-    float const error_margin = 0.01;
+    float const error_margin = 0.1;
     float const luma_gray = 0.50;
+    static constexpr std::chrono::milliseconds EVENT_WAIT_TIME_MS = 5000ms;
 };
 
 TEST_F(RegionSamplingTest, invalidLayerHandle_doesNotCrash) {
@@ -261,7 +262,7 @@
     composer->removeRegionSamplingListener(listener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_CollectsLuma) {
+TEST_F(RegionSamplingTest, CollectsLuma) {
     fill_render(rgba_green);
 
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
@@ -273,7 +274,30 @@
     sampleArea.bottom = 200;
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
+    EXPECT_NEAR(listener->luma(), luma_green, error_margin);
+
+    composer->removeRegionSamplingListener(listener);
+}
+
+TEST_F(RegionSamplingTest, CollectsLumaForSecureLayer) {
+    fill_render(rgba_green);
+    SurfaceComposerClient::Transaction()
+            .setFlags(mContentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+            .apply(/*synchronous=*/true);
+
+    sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
+    sp<Listener> listener = new Listener();
+    gui::ARect sampleArea;
+    sampleArea.left = 100;
+    sampleArea.top = 100;
+    sampleArea.right = 200;
+    sampleArea.bottom = 200;
+    composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
+
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     composer->removeRegionSamplingListener(listener);
@@ -291,13 +315,14 @@
     sampleArea.bottom = 200;
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     listener->reset();
 
     fill_render(rgba_blue);
-    EXPECT_TRUE(listener->wait_event(300ms))
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for 2nd luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
 
@@ -323,10 +348,10 @@
     graySampleArea.bottom = 200;
     composer->addRegionSamplingListener(graySampleArea, mTopLayer->getHandle(), grayListener);
 
-    EXPECT_TRUE(grayListener->wait_event(300ms))
+    EXPECT_TRUE(grayListener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for luma event to be received";
     EXPECT_NEAR(grayListener->luma(), luma_gray, error_margin);
-    EXPECT_TRUE(greenListener->wait_event(300ms))
+    EXPECT_TRUE(greenListener->wait_event(EVENT_WAIT_TIME_MS))
             << "timed out waiting for luma event to be received";
     EXPECT_NEAR(greenListener->luma(), luma_green, error_margin);
 
@@ -334,7 +359,7 @@
     composer->removeRegionSamplingListener(grayListener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_TestIfInvalidInputParameters) {
+TEST_F(RegionSamplingTest, TestIfInvalidInputParameters) {
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
     sp<Listener> listener = new Listener();
 
@@ -369,7 +394,7 @@
     composer->removeRegionSamplingListener(listener);
 }
 
-TEST_F(RegionSamplingTest, DISABLED_TestCallbackAfterRemoveListener) {
+TEST_F(RegionSamplingTest, TestCallbackAfterRemoveListener) {
     fill_render(rgba_green);
     sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
     sp<Listener> listener = new Listener();
@@ -381,7 +406,8 @@
     composer->addRegionSamplingListener(sampleArea, mTopLayer->getHandle(), listener);
     fill_render(rgba_green);
 
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
 
     listener->reset();
@@ -404,11 +430,13 @@
     // Test: listener in (100, 100). See layer before move, no layer after move.
     fill_render(rgba_blue);
     composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_blue, error_margin);
     listener->reset();
     SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
     composer->removeRegionSamplingListener(listener);
 
@@ -420,11 +448,13 @@
     sampleAreaA.right = sampleArea.right;
     sampleAreaA.bottom = sampleArea.bottom;
     composer->addRegionSamplingListener(sampleAreaA, mTopLayer->getHandle(), listener);
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_gray, error_margin);
     listener->reset();
     SurfaceComposerClient::Transaction{}.setPosition(mContentLayer, 600, 600).apply();
-    EXPECT_TRUE(listener->wait_event(300ms)) << "timed out waiting for luma event to be received";
+    EXPECT_TRUE(listener->wait_event(EVENT_WAIT_TIME_MS))
+            << "timed out waiting for luma event to be received";
     EXPECT_NEAR(listener->luma(), luma_green, error_margin);
     composer->removeRegionSamplingListener(listener);
 }
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index b28dca8..59d05b6 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -40,12 +40,8 @@
     }
 
     virtual void SetUp() {
-        sp<IGraphicBufferProducer> producer;
-        sp<IGraphicBufferConsumer> consumer;
-        BufferQueue::createBufferQueue(&producer, &consumer);
-        mST = new GLConsumer(consumer, 123, GLConsumer::TEXTURE_EXTERNAL, true,
-                false);
-        mSTC = new Surface(producer);
+        mST = new GLConsumer(123, GLConsumer::TEXTURE_EXTERNAL, true, false);
+        mSTC = mST->getSurface();
         mANW = mSTC;
 
         // We need a valid GL context so we can test updateTexImage()
@@ -731,12 +727,8 @@
         ASSERT_NE(EGL_NO_CONTEXT, mEglContext);
 
         for (int i = 0; i < NUM_SURFACE_TEXTURES; i++) {
-            sp<IGraphicBufferProducer> producer;
-            sp<IGraphicBufferConsumer> consumer;
-            BufferQueue::createBufferQueue(&producer, &consumer);
-            sp<GLConsumer> st(new GLConsumer(consumer, i,
-                    GLConsumer::TEXTURE_EXTERNAL, true, false));
-            sp<Surface> stc(new Surface(producer));
+            sp<GLConsumer> st(new GLConsumer(i, GLConsumer::TEXTURE_EXTERNAL, true, false));
+            sp<Surface> stc = st->getSurface();
             mEglSurfaces[i] = eglCreateWindowSurface(mEglDisplay, myConfig,
                     static_cast<ANativeWindow*>(stc.get()), nullptr);
             ASSERT_EQ(EGL_SUCCESS, eglGetError());
diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h
index 9d8af5d..1309635 100644
--- a/libs/gui/tests/SurfaceTextureGL.h
+++ b/libs/gui/tests/SurfaceTextureGL.h
@@ -38,11 +38,8 @@
 
     void SetUp() {
         GLTest::SetUp();
-        sp<IGraphicBufferProducer> producer;
-        BufferQueue::createBufferQueue(&producer, &mConsumer);
-        mST = new GLConsumer(mConsumer, TEX_ID, GLConsumer::TEXTURE_EXTERNAL,
-                true, false);
-        mSTC = new Surface(producer);
+        mST = new GLConsumer(TEX_ID, GLConsumer::TEXTURE_EXTERNAL, true, false);
+        mSTC = mST->getSurface();
         mANW = mSTC;
         ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), TEST_PRODUCER_USAGE_BITS));
         mTextureRenderer = new TextureRenderer(TEX_ID, mST);
@@ -63,7 +60,6 @@
         mTextureRenderer->drawTexture();
     }
 
-    sp<IGraphicBufferConsumer> mConsumer;
     sp<GLConsumer> mST;
     sp<Surface> mSTC;
     sp<ANativeWindow> mANW;
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
index f76c0be..449533a 100644
--- a/libs/gui/tests/SurfaceTextureGL_test.cpp
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -480,8 +480,8 @@
     };
 
     sp<DisconnectWaiter> dw(new DisconnectWaiter());
-    mConsumer->consumerConnect(dw, false);
-
+    sp<IGraphicBufferConsumer> consumer = mST->getIGraphicBufferConsumer();
+    consumer->consumerConnect(dw, false);
 
     sp<Thread> pt(new ProducerThread(mANW));
     pt->run("ProducerThread");
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 577d239..88893b6 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "gui/view/Surface.h"
 #include "Constants.h"
 #include "MockConsumer.h"
 
@@ -23,28 +24,41 @@
 #include <android/gui/IDisplayEventConnection.h>
 #include <android/gui/ISurfaceComposer.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
+#include <android/hardware_buffer.h>
 #include <binder/ProcessState.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <configstore/Utils.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/BufferItemConsumer.h>
-#include <gui/IProducerListener.h>
+#include <gui/BufferQueue.h>
+#include <gui/CpuConsumer.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
-#include <inttypes.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <sys/types.h>
+#include <system/window.h>
 #include <ui/BufferQueueDefs.h>
 #include <ui/DisplayMode.h>
+#include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
 #include <utils/Errors.h>
 #include <utils/String8.h>
 
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <future>
 #include <limits>
 #include <thread>
 
+#include "testserver/TestServerClient.h"
+
 namespace android {
 
 using namespace std::chrono_literals;
@@ -82,7 +96,7 @@
     virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>& buffers) {
         mDiscardedBuffers.insert(mDiscardedBuffers.end(), buffers.begin(), buffers.end());
     }
-
+    virtual void onBufferDetached(int /*slot*/) {}
     int getReleaseNotifyCount() const {
         return mBuffersReleased;
     }
@@ -97,6 +111,18 @@
     std::vector<sp<GraphicBuffer>> mDiscardedBuffers;
 };
 
+class DeathWatcherListener : public StubSurfaceListener {
+public:
+    virtual void onRemoteDied() { mDiedPromise.set_value(true); }
+
+    virtual bool needsDeathNotify() { return true; }
+
+    std::future<bool> getDiedFuture() { return mDiedPromise.get_future(); }
+
+private:
+    std::promise<bool> mDiedPromise;
+};
+
 class SurfaceTest : public ::testing::Test {
 protected:
     SurfaceTest() {
@@ -143,10 +169,10 @@
         if (hasSurfaceListener) {
             listener = new FakeSurfaceListener(enableReleasedCb);
         }
-        ASSERT_EQ(OK, surface->connect(
-                NATIVE_WINDOW_API_CPU,
-                /*reportBufferRemoval*/true,
-                /*listener*/listener));
+        ASSERT_EQ(OK,
+                  surface->connect(NATIVE_WINDOW_API_CPU,
+                                   /*listener*/ listener,
+                                   /*reportBufferRemoval*/ true));
         const int BUFFER_COUNT = 4 + extraDiscardedBuffers;
         ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
         ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
@@ -173,7 +199,7 @@
         // Acquire and free 1+extraDiscardedBuffers buffer, check onBufferReleased is called.
         std::vector<BufferItem> releasedItems;
         releasedItems.resize(1+extraDiscardedBuffers);
-        for (int i = 0; i < releasedItems.size(); i++) {
+        for (size_t i = 0; i < releasedItems.size(); i++) {
             ASSERT_EQ(NO_ERROR, consumer->acquireBuffer(&releasedItems[i], 0));
             ASSERT_EQ(NO_ERROR, consumer->releaseBuffer(releasedItems[i].mSlot,
                     releasedItems[i].mFrameNumber, EGL_NO_DISPLAY, EGL_NO_SYNC_KHR,
@@ -197,7 +223,7 @@
             // Check onBufferDiscarded is called with correct buffer
             auto discardedBuffers = listener->getDiscardedBuffers();
             ASSERT_EQ(discardedBuffers.size(), releasedItems.size());
-            for (int i = 0; i < releasedItems.size(); i++) {
+            for (size_t i = 0; i < releasedItems.size(); i++) {
                 ASSERT_EQ(discardedBuffers[i], releasedItems[i].mGraphicBuffer);
             }
 
@@ -264,13 +290,9 @@
 TEST_F(SurfaceTest, QueryConsumerUsage) {
     const int TEST_USAGE_FLAGS =
             GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER;
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-    sp<BufferItemConsumer> c = new BufferItemConsumer(consumer,
-            TEST_USAGE_FLAGS);
-    sp<Surface> s = new Surface(producer);
+    sp<BufferItemConsumer> c = new BufferItemConsumer(TEST_USAGE_FLAGS);
 
+    sp<Surface> s = c->getSurface();
     sp<ANativeWindow> anw(s);
 
     int flags = -1;
@@ -282,15 +304,11 @@
 
 TEST_F(SurfaceTest, QueryDefaultBuffersDataSpace) {
     const android_dataspace TEST_DATASPACE = HAL_DATASPACE_V0_SRGB;
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
 
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(1);
     cpuConsumer->setDefaultBufferDataSpace(TEST_DATASPACE);
 
-    sp<Surface> s = new Surface(producer);
-
+    sp<Surface> s = cpuConsumer->getSurface();
     sp<ANativeWindow> anw(s);
 
     android_dataspace dataSpace;
@@ -303,11 +321,8 @@
 }
 
 TEST_F(SurfaceTest, SettingGenerationNumber) {
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
-    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
-    sp<Surface> surface = new Surface(producer);
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(1);
+    sp<Surface> surface = cpuConsumer->getSurface();
     sp<ANativeWindow> window(surface);
 
     // Allocate a buffer with a generation number of 0
@@ -491,11 +506,11 @@
 
     sp<Surface> surface = new Surface(producer);
     sp<ANativeWindow> window(surface);
-    sp<StubProducerListener> listener = new StubProducerListener();
-    ASSERT_EQ(OK, surface->connect(
-            NATIVE_WINDOW_API_CPU,
-            /*listener*/listener,
-            /*reportBufferRemoval*/true));
+    sp<StubSurfaceListener> listener = new StubSurfaceListener();
+    ASSERT_EQ(OK,
+              surface->connect(NATIVE_WINDOW_API_CPU,
+                               /*listener*/ listener,
+                               /*reportBufferRemoval*/ true));
     const int BUFFER_COUNT = 4;
     ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
     ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
@@ -636,7 +651,7 @@
 
     status_t setTransactionState(
             const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
-            const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+            Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
             const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
             int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
             const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
@@ -673,13 +688,14 @@
         return binder::Status::ok();
     }
 
-    binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
-                                 float /*requestedRefreshRate*/,
-                                 sp<IBinder>* /*outDisplay*/) override {
+    binder::Status createVirtualDisplay(const std::string& /*displayName*/, bool /*isSecure*/,
+                                        const std::string& /*uniqueId*/,
+                                        float /*requestedRefreshRate*/,
+                                        sp<IBinder>* /*outDisplay*/) override {
         return binder::Status::ok();
     }
 
-    binder::Status destroyDisplay(const sp<IBinder>& /*display*/) override {
+    binder::Status destroyVirtualDisplay(const sp<IBinder>& /*displayToken*/) override {
         return binder::Status::ok();
     }
 
@@ -815,10 +831,6 @@
         return binder::Status::ok();
     }
 
-    binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* /*outLayers*/) override {
-        return binder::Status::ok();
-    }
-
     binder::Status getCompositionPreference(gui::CompositionPreference* /*outPref*/) override {
         return binder::Status::ok();
     }
@@ -988,6 +1000,21 @@
         return binder::Status::ok();
     }
 
+    binder::Status notifyShutdown() override { return binder::Status::ok(); }
+
+    binder::Status addJankListener(const sp<IBinder>& /*layer*/,
+                                   const sp<gui::IJankListener>& /*listener*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status flushJankData(int32_t /*layerId*/) override { return binder::Status::ok(); }
+
+    binder::Status removeJankListener(int32_t /*layerId*/,
+                                      const sp<gui::IJankListener>& /*listener*/,
+                                      int64_t /*afterVsync*/) override {
+        return binder::Status::ok();
+    }
+
 protected:
     IBinder* onAsBinder() override { return nullptr; }
 
@@ -2135,14 +2162,11 @@
 TEST_F(SurfaceTest, BatchOperations) {
     const int BUFFER_COUNT = 16;
     const int BATCH_SIZE = 8;
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
 
-    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
-    sp<Surface> surface = new Surface(producer);
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(1);
+    sp<Surface> surface = cpuConsumer->getSurface();
     sp<ANativeWindow> window(surface);
-    sp<StubProducerListener> listener = new StubProducerListener();
+    sp<StubSurfaceListener> listener = new StubSurfaceListener();
 
     ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
             /*reportBufferRemoval*/false));
@@ -2187,14 +2211,11 @@
 TEST_F(SurfaceTest, BatchIllegalOperations) {
     const int BUFFER_COUNT = 16;
     const int BATCH_SIZE = 8;
-    sp<IGraphicBufferProducer> producer;
-    sp<IGraphicBufferConsumer> consumer;
-    BufferQueue::createBufferQueue(&producer, &consumer);
 
-    sp<CpuConsumer> cpuConsumer = new CpuConsumer(consumer, 1);
-    sp<Surface> surface = new Surface(producer);
+    sp<CpuConsumer> cpuConsumer = new CpuConsumer(1);
+    sp<Surface> surface = cpuConsumer->getSurface();
     sp<ANativeWindow> window(surface);
-    sp<StubProducerListener> listener = new StubProducerListener();
+    sp<StubSurfaceListener> listener = new StubSurfaceListener();
 
     ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, /*listener*/listener,
             /*reportBufferRemoval*/false));
@@ -2214,4 +2235,288 @@
     ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
+TEST_F(SurfaceTest, PlatformBufferMethods) {
+    sp<CpuConsumer> cpuConsumer = sp<CpuConsumer>::make(1);
+    sp<Surface> surface = cpuConsumer->getSurface();
+    sp<StubSurfaceListener> listener = sp<StubSurfaceListener>::make();
+    sp<GraphicBuffer> buffer;
+    sp<Fence> fence;
+
+    EXPECT_EQ(OK,
+              surface->connect(NATIVE_WINDOW_API_CPU, listener, /* reportBufferRemoval */ false));
+
+    //
+    // Verify nullptrs are handled safely:
+    //
+
+    EXPECT_EQ(BAD_VALUE, surface->dequeueBuffer((sp<GraphicBuffer>*)nullptr, nullptr));
+    EXPECT_EQ(BAD_VALUE, surface->dequeueBuffer((sp<GraphicBuffer>*)nullptr, &fence));
+    EXPECT_EQ(BAD_VALUE, surface->dequeueBuffer(&buffer, nullptr));
+    EXPECT_EQ(BAD_VALUE, surface->queueBuffer(nullptr, nullptr));
+    EXPECT_EQ(BAD_VALUE, surface->detachBuffer(nullptr));
+
+    //
+    // Verify dequeue/queue:
+    //
+
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+    EXPECT_NE(nullptr, buffer);
+    EXPECT_EQ(OK, surface->queueBuffer(buffer, fence));
+
+    //
+    // Verify dequeue/detach:
+    //
+
+    wp<GraphicBuffer> weakBuffer;
+    {
+        EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+
+        EXPECT_EQ(OK, surface->detachBuffer(buffer));
+
+        weakBuffer = buffer;
+        buffer = nullptr;
+    }
+    EXPECT_EQ(nullptr, weakBuffer.promote()) << "Weak buffer still held by Surface.";
+
+    //
+    // Verify detach without borrowing the buffer does not work:
+    //
+
+    sp<GraphicBuffer> heldTooLongBuffer;
+    EXPECT_EQ(OK, surface->dequeueBuffer(&heldTooLongBuffer, &fence));
+    EXPECT_EQ(OK, surface->queueBuffer(heldTooLongBuffer));
+    EXPECT_EQ(BAD_VALUE, surface->detachBuffer(heldTooLongBuffer));
+}
+
+TEST_F(SurfaceTest, AllowAllocation) {
+    // controlledByApp must be true to disable blocking
+    sp<CpuConsumer> cpuConsumer = sp<CpuConsumer>::make(1, /*controlledByApp*/ true);
+    sp<Surface> surface = cpuConsumer->getSurface();
+    sp<StubSurfaceListener> listener = sp<StubSurfaceListener>::make();
+    sp<GraphicBuffer> buffer;
+    sp<Fence> fence;
+
+    EXPECT_EQ(OK,
+              surface->connect(NATIVE_WINDOW_API_CPU, listener, /* reportBufferRemoval */ false));
+    EXPECT_EQ(OK, surface->allowAllocation(false));
+
+    EXPECT_EQ(OK, surface->setDequeueTimeout(-1));
+    EXPECT_EQ(WOULD_BLOCK, surface->dequeueBuffer(&buffer, &fence));
+
+    EXPECT_EQ(OK, surface->setDequeueTimeout(10));
+    EXPECT_EQ(TIMED_OUT, surface->dequeueBuffer(&buffer, &fence));
+
+    EXPECT_EQ(OK, surface->allowAllocation(true));
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+}
+
+TEST_F(SurfaceTest, QueueAcquireReleaseDequeue_CalledInStack_DoesNotDeadlock) {
+    class DequeuingSurfaceListener : public SurfaceListener {
+    public:
+        DequeuingSurfaceListener(const wp<Surface>& surface) : mSurface(surface) {}
+
+        virtual void onBufferReleased() override {
+            sp<Surface> surface = mSurface.promote();
+            ASSERT_NE(nullptr, surface);
+            EXPECT_EQ(OK, surface->dequeueBuffer(&mBuffer, &mFence));
+        }
+
+        virtual bool needsReleaseNotify() override { return true; }
+        virtual void onBuffersDiscarded(const std::vector<sp<GraphicBuffer>>&) override {}
+        virtual void onBufferDetached(int) override {}
+
+        sp<GraphicBuffer> mBuffer;
+        sp<Fence> mFence;
+
+    private:
+        wp<Surface> mSurface;
+    };
+
+    class ImmediateReleaseConsumerListener : public BufferItemConsumer::FrameAvailableListener {
+    public:
+        ImmediateReleaseConsumerListener(const wp<BufferItemConsumer>& consumer)
+              : mConsumer(consumer) {}
+
+        virtual void onFrameAvailable(const BufferItem&) override {
+            sp<BufferItemConsumer> consumer = mConsumer.promote();
+            ASSERT_NE(nullptr, consumer);
+
+            mCalls += 1;
+
+            BufferItem buffer;
+            EXPECT_EQ(OK, consumer->acquireBuffer(&buffer, 0));
+            EXPECT_EQ(OK, consumer->releaseBuffer(buffer));
+        }
+
+        size_t mCalls = 0;
+
+    private:
+        wp<BufferItemConsumer> mConsumer;
+    };
+
+    sp<IGraphicBufferProducer> bqProducer;
+    sp<IGraphicBufferConsumer> bqConsumer;
+    BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
+
+    sp<BufferItemConsumer> consumer = sp<BufferItemConsumer>::make(bqConsumer, 3);
+    sp<Surface> surface = sp<Surface>::make(bqProducer);
+    sp<ImmediateReleaseConsumerListener> consumerListener =
+            sp<ImmediateReleaseConsumerListener>::make(consumer);
+    consumer->setFrameAvailableListener(consumerListener);
+
+    sp<DequeuingSurfaceListener> surfaceListener = sp<DequeuingSurfaceListener>::make(surface);
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, surfaceListener, false));
+
+    EXPECT_EQ(OK, surface->setMaxDequeuedBufferCount(2));
+
+    sp<GraphicBuffer> buffer;
+    sp<Fence> fence;
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+    EXPECT_EQ(OK, surface->queueBuffer(buffer, fence));
+
+    EXPECT_EQ(1u, consumerListener->mCalls);
+    EXPECT_NE(nullptr, surfaceListener->mBuffer);
+
+    EXPECT_EQ(OK, surface->disconnect(NATIVE_WINDOW_API_CPU));
+}
+
+TEST_F(SurfaceTest, ViewSurface_toString) {
+    view::Surface surface{};
+    EXPECT_EQ("", surface.toString());
+
+    surface.name = String16("name");
+    EXPECT_EQ("name", surface.toString());
+}
+
+TEST_F(SurfaceTest, TestRemoteSurfaceDied_CallbackCalled) {
+    sp<TestServerClient> testServer = TestServerClient::Create();
+    sp<IGraphicBufferProducer> producer = testServer->CreateProducer();
+    EXPECT_NE(nullptr, producer);
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<DeathWatcherListener> deathWatcher = sp<DeathWatcherListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, deathWatcher));
+
+    auto diedFuture = deathWatcher->getDiedFuture();
+    EXPECT_EQ(OK, testServer->Kill());
+
+    diedFuture.wait();
+    EXPECT_TRUE(diedFuture.get());
+}
+
+TEST_F(SurfaceTest, TestRemoteSurfaceDied_Disconnect_CallbackNotCalled) {
+    sp<TestServerClient> testServer = TestServerClient::Create();
+    sp<IGraphicBufferProducer> producer = testServer->CreateProducer();
+    EXPECT_NE(nullptr, producer);
+
+    sp<Surface> surface = sp<Surface>::make(producer);
+    sp<DeathWatcherListener> deathWatcher = sp<DeathWatcherListener>::make();
+    EXPECT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, deathWatcher));
+    EXPECT_EQ(OK, surface->disconnect(NATIVE_WINDOW_API_CPU));
+
+    auto watcherDiedFuture = deathWatcher->getDiedFuture();
+    EXPECT_EQ(OK, testServer->Kill());
+
+    std::future_status status = watcherDiedFuture.wait_for(std::chrono::seconds(1));
+    EXPECT_EQ(std::future_status::timeout, status);
+}
+
+TEST_F(SurfaceTest, QueueBufferOutput_TracksReplacements) {
+    sp<BufferItemConsumer> consumer = sp<BufferItemConsumer>::make(GRALLOC_USAGE_SW_READ_OFTEN);
+    ASSERT_EQ(OK, consumer->setMaxBufferCount(3));
+    ASSERT_EQ(OK, consumer->setMaxAcquiredBufferCount(1));
+
+    sp<Surface> surface = consumer->getSurface();
+    sp<StubSurfaceListener> listener = sp<StubSurfaceListener>::make();
+
+    // Async mode sets up an extra buffer so the surface can queue it without waiting.
+    ASSERT_EQ(OK, surface->setMaxDequeuedBufferCount(1));
+    ASSERT_EQ(OK, surface->setAsyncMode(true));
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, listener));
+
+    sp<GraphicBuffer> buffer;
+    sp<Fence> fence;
+    SurfaceQueueBufferOutput output;
+    BufferItem item;
+
+    // We can queue directly, without an output arg.
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+    EXPECT_EQ(OK, surface->queueBuffer(buffer, fence));
+    EXPECT_EQ(OK, consumer->acquireBuffer(&item, 0));
+    EXPECT_EQ(OK, consumer->releaseBuffer(item));
+
+    // We can queue with an output arg, and that we don't expect to see a replacement.
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+    EXPECT_EQ(OK, surface->queueBuffer(buffer, fence, &output));
+    EXPECT_FALSE(output.bufferReplaced);
+
+    // We expect see a replacement when we queue a second buffer in async mode, and the consumer
+    // hasn't acquired the first one yet.
+    EXPECT_EQ(OK, surface->dequeueBuffer(&buffer, &fence));
+    EXPECT_EQ(OK, surface->queueBuffer(buffer, fence, &output));
+    EXPECT_TRUE(output.bufferReplaced);
+}
+
+TEST_F(SurfaceTest, QueueBufferOutput_TracksReplacements_Plural) {
+    sp<BufferItemConsumer> consumer = sp<BufferItemConsumer>::make(GRALLOC_USAGE_SW_READ_OFTEN);
+    ASSERT_EQ(OK, consumer->setMaxBufferCount(4));
+    ASSERT_EQ(OK, consumer->setMaxAcquiredBufferCount(1));
+
+    sp<Surface> surface = consumer->getSurface();
+    consumer->setName(String8("TRPTest"));
+    sp<StubSurfaceListener> listener = sp<StubSurfaceListener>::make();
+
+    // Async mode sets up an extra buffer so the surface can queue it without waiting.
+    ASSERT_EQ(OK, surface->setMaxDequeuedBufferCount(2));
+    ASSERT_EQ(OK, surface->setAsyncMode(true));
+    ASSERT_EQ(OK, surface->connect(NATIVE_WINDOW_API_CPU, listener));
+
+    // dequeueBuffers requires a vector of a certain size:
+    std::vector<Surface::BatchBuffer> buffers(2);
+    std::vector<Surface::BatchQueuedBuffer> queuedBuffers;
+    std::vector<SurfaceQueueBufferOutput> outputs;
+    BufferItem item;
+
+    auto moveBuffersToQueuedBuffers = [&]() {
+        EXPECT_EQ(2u, buffers.size());
+        EXPECT_NE(nullptr, buffers[0].buffer);
+        EXPECT_NE(nullptr, buffers[1].buffer);
+
+        queuedBuffers.clear();
+        for (auto& buffer : buffers) {
+            auto& queuedBuffer = queuedBuffers.emplace_back();
+            queuedBuffer.buffer = buffer.buffer;
+            queuedBuffer.fenceFd = buffer.fenceFd;
+            queuedBuffer.timestamp = NATIVE_WINDOW_TIMESTAMP_AUTO;
+        }
+        buffers = {{}, {}};
+    };
+
+    // We can queue directly, without an output arg.
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+    moveBuffersToQueuedBuffers();
+    EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers));
+    EXPECT_EQ(OK, consumer->acquireBuffer(&item, 0));
+    EXPECT_EQ(OK, consumer->releaseBuffer(item));
+
+    // We can queue with an output arg. Only the second one should be replaced.
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+    moveBuffersToQueuedBuffers();
+    EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers, &outputs));
+    EXPECT_EQ(2u, outputs.size());
+    EXPECT_FALSE(outputs[0].bufferReplaced);
+    EXPECT_TRUE(outputs[1].bufferReplaced);
+
+    // Since we haven't acquired anything, both queued buffers will replace the original one.
+    EXPECT_EQ(OK, surface->dequeueBuffers(&buffers));
+    moveBuffersToQueuedBuffers();
+    EXPECT_EQ(OK, surface->queueBuffers(queuedBuffers, &outputs));
+    EXPECT_EQ(2u, outputs.size());
+    EXPECT_TRUE(outputs[0].bufferReplaced);
+    EXPECT_TRUE(outputs[1].bufferReplaced);
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS)
+
 } // namespace android
diff --git a/libs/gui/tests/TestServer_test.cpp b/libs/gui/tests/TestServer_test.cpp
new file mode 100644
index 0000000..d640782
--- /dev/null
+++ b/libs/gui/tests/TestServer_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <SurfaceFlingerProperties.h>
+#include <android/gui/IDisplayEventConnection.h>
+#include <android/gui/ISurfaceComposer.h>
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
+#include <android/hardware_buffer.h>
+#include <binder/ProcessState.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <configstore/Utils.h>
+#include <gui/AidlUtil.h>
+#include <gui/BufferItemConsumer.h>
+#include <gui/BufferQueue.h>
+#include <gui/CpuConsumer.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/ISurfaceComposer.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SyncScreenCaptureListener.h>
+#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
+#include <sys/types.h>
+#include <system/window.h>
+#include <ui/BufferQueueDefs.h>
+#include <ui/DisplayMode.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/Rect.h>
+#include <utils/Errors.h>
+#include <utils/String8.h>
+
+#include <cstddef>
+#include <limits>
+#include <thread>
+
+#include "binder/IInterface.h"
+#include "testserver/TestServerClient.h"
+
+namespace android {
+
+namespace {
+
+class TestServerTest : public ::testing::Test {
+protected:
+    TestServerTest() { ProcessState::self()->startThreadPool(); }
+};
+
+} // namespace
+
+TEST_F(TestServerTest, Create) {
+    EXPECT_NE(nullptr, TestServerClient::Create());
+}
+
+TEST_F(TestServerTest, CreateProducer) {
+    sp<TestServerClient> client = TestServerClient::Create();
+    EXPECT_NE(nullptr, client->CreateProducer());
+}
+
+TEST_F(TestServerTest, KillServer) {
+    class DeathWaiter : public IBinder::DeathRecipient {
+    public:
+        virtual void binderDied(const wp<IBinder>&) override { mPromise.set_value(true); }
+        std::future<bool> getFuture() { return mPromise.get_future(); }
+
+        std::promise<bool> mPromise;
+    };
+
+    sp<TestServerClient> client = TestServerClient::Create();
+    sp<IGraphicBufferProducer> producer = client->CreateProducer();
+    EXPECT_NE(nullptr, producer);
+
+    sp<DeathWaiter> deathWaiter = sp<DeathWaiter>::make();
+    EXPECT_EQ(OK, IInterface::asBinder(producer)->linkToDeath(deathWaiter));
+
+    auto deathWaiterFuture = deathWaiter->getFuture();
+    EXPECT_EQ(OK, client->Kill());
+    EXPECT_EQ(nullptr, client->CreateProducer());
+
+    EXPECT_TRUE(deathWaiterFuture.get());
+}
+
+} // namespace android
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 5eb5d3b..ce22082 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -64,7 +64,7 @@
     i.ownerUid = gui::Uid{24};
     i.packageName = "com.example.package";
     i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE;
-    i.displayId = 34;
+    i.displayId = ui::LogicalDisplayId{34};
     i.replaceTouchableRegionWithCrop = true;
     i.touchableRegionCropHandle = touchableRegionCropHandle;
     i.applicationInfo.name = "ApplicationFooBar";
diff --git a/libs/gui/tests/testserver/TestServer.cpp b/libs/gui/tests/testserver/TestServer.cpp
new file mode 100644
index 0000000..cd8824e
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServer.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TestServer"
+
+#include <android-base/stringprintf.h>
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/Status.h>
+#include <gui/BufferQueue.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/view/Surface.h>
+#include <libgui_test_server/BnTestServer.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+
+#include <cstdint>
+#include <cstdlib>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "TestServer.h"
+
+namespace android {
+
+namespace {
+class TestConsumerListener : public BnConsumerListener {
+    virtual void onFrameAvailable(const BufferItem&) override {}
+    virtual void onBuffersReleased() override {}
+    virtual void onSidebandStreamChanged() override {}
+};
+
+class TestServiceImpl : public libgui_test_server::BnTestServer {
+public:
+    TestServiceImpl(const char* name) : mName(name) {}
+
+    virtual binder::Status createProducer(view::Surface* out) override {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        BufferQueueHolder bq;
+        BufferQueue::createBufferQueue(&bq.producer, &bq.consumer);
+        sp<TestConsumerListener> listener = sp<TestConsumerListener>::make();
+        bq.consumer->consumerConnect(listener, /*controlledByApp*/ true);
+
+        uint64_t id = 0;
+        bq.producer->getUniqueId(&id);
+        std::string name = base::StringPrintf("%s-%" PRIu64, mName, id);
+
+        out->name = String16(name.c_str());
+        out->graphicBufferProducer = bq.producer;
+        mBqs.push_back(std::move(bq));
+
+        return binder::Status::ok();
+    }
+
+    virtual binder::Status killNow() override {
+        ALOGE("LibGUI Test Service %s dying in response to killNow", mName);
+        _exit(0);
+        // Not reached:
+        return binder::Status::ok();
+    }
+
+private:
+    std::mutex mMutex;
+    const char* mName;
+
+    struct BufferQueueHolder {
+        sp<IGraphicBufferProducer> producer;
+        sp<IGraphicBufferConsumer> consumer;
+    };
+
+    std::vector<BufferQueueHolder> mBqs;
+};
+} // namespace
+
+int TestServerMain(const char* name) {
+    ProcessState::self()->startThreadPool();
+
+    sp<TestServiceImpl> testService = sp<TestServiceImpl>::make(name);
+    ALOGE("service");
+    sp<IServiceManager> serviceManager(defaultServiceManager());
+    LOG_ALWAYS_FATAL_IF(OK != serviceManager->addService(String16(name), testService));
+
+    ALOGD("LibGUI Test Service %s STARTED", name);
+
+    IPCThreadState::self()->joinThreadPool();
+
+    ALOGW("LibGUI Test Service %s DIED", name);
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/testserver/TestServer.h b/libs/gui/tests/testserver/TestServer.h
new file mode 100644
index 0000000..4226f1b
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServer.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+
+/*
+ * Main method for a libgui ITestServer server.
+ *
+ * This must be called without any binder setup having been done, because you can't fork and do
+ * binder things once ProcessState is set up.
+ * @param name The service name of the test server to start.
+ * @return retcode
+ */
+int TestServerMain(const char* name);
+
+} // namespace android
diff --git a/libs/gui/tests/testserver/TestServerClient.cpp b/libs/gui/tests/testserver/TestServerClient.cpp
new file mode 100644
index 0000000..e388074
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServerClient.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2024 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 <sys/wait.h>
+#include <cerrno>
+#define LOG_TAG "TestServerClient"
+
+#include <android-base/stringprintf.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <libgui_test_server/ITestServer.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <atomic>
+#include <csignal>
+#include <cstdlib>
+#include <mutex>
+#include <string>
+
+#include "TestServerClient.h"
+#include "TestServerCommon.h"
+
+namespace android {
+
+namespace {
+
+std::string GetUniqueServiceName() {
+    static std::atomic<int> uniqueId = 1;
+
+    pid_t pid = getpid();
+    int id = uniqueId++;
+    return base::StringPrintf("Libgui-TestServer-%d-%d", pid, id);
+}
+
+struct RemoteTestServerHostHolder {
+    RemoteTestServerHostHolder(pid_t pid, int sendFd, int recvFd)
+          : mPid(pid), mSendFd(sendFd), mRecvFd(recvFd) {}
+    ~RemoteTestServerHostHolder() {
+        std::lock_guard lock(mMutex);
+
+        kill(mPid, SIGKILL);
+        close(mSendFd);
+        close(mRecvFd);
+    }
+
+    pid_t CreateTestServerOrDie(std::string name) {
+        std::lock_guard lock(mMutex);
+
+        CreateServerRequest request;
+        strlcpy(request.name, name.c_str(), sizeof(request.name) / sizeof(request.name[0]));
+
+        ssize_t bytes = write(mSendFd, &request, sizeof(request));
+        LOG_ALWAYS_FATAL_IF(bytes != sizeof(request));
+
+        CreateServerResponse response;
+        bytes = read(mRecvFd, &response, sizeof(response));
+        LOG_ALWAYS_FATAL_IF(bytes != sizeof(response));
+
+        return response.pid;
+    }
+
+private:
+    std::mutex mMutex;
+
+    pid_t mPid;
+    int mSendFd;
+    int mRecvFd;
+};
+
+std::unique_ptr<RemoteTestServerHostHolder> g_remoteTestServerHostHolder = nullptr;
+
+} // namespace
+
+void TestServerClient::InitializeOrDie(const char* filename) {
+    int sendPipeFds[2];
+    int ret = pipe(sendPipeFds);
+    LOG_ALWAYS_FATAL_IF(ret, "Unable to create subprocess send pipe");
+
+    int recvPipeFds[2];
+    ret = pipe(recvPipeFds);
+    LOG_ALWAYS_FATAL_IF(ret, "Unable to create subprocess recv pipe");
+
+    pid_t childPid = fork();
+    LOG_ALWAYS_FATAL_IF(childPid < 0, "Unable to fork child process");
+
+    if (childPid == 0) {
+        // We forked!
+        close(sendPipeFds[1]);
+        close(recvPipeFds[0]);
+
+        // We'll be reading from the parent's "send" and writing to the parent's "recv".
+        std::string sendPipe = std::to_string(sendPipeFds[0]);
+        std::string recvPipe = std::to_string(recvPipeFds[1]);
+        char* args[] = {
+                const_cast<char*>(filename),
+                const_cast<char*>("--test-server-host"),
+                const_cast<char*>(sendPipe.c_str()),
+                const_cast<char*>(recvPipe.c_str()),
+                nullptr,
+        };
+
+        ret = execv(filename, args);
+        ALOGE("Failed to exec libguiTestServer. ret=%d errno=%d (%s)", ret, errno, strerror(errno));
+        status_t status = -errno;
+        write(recvPipeFds[1], &status, sizeof(status));
+        _exit(EXIT_FAILURE);
+    }
+
+    close(sendPipeFds[0]);
+    close(recvPipeFds[1]);
+
+    // Check for an OK status that the host started. If so, we're good to go.
+    status_t status;
+    ret = read(recvPipeFds[0], &status, sizeof(status));
+    LOG_ALWAYS_FATAL_IF(ret != sizeof(status), "Unable to read from pipe: %d", ret);
+    LOG_ALWAYS_FATAL_IF(OK != status, "Pipe returned failed status: %d", status);
+
+    g_remoteTestServerHostHolder =
+            std::make_unique<RemoteTestServerHostHolder>(childPid, sendPipeFds[1], recvPipeFds[0]);
+}
+
+sp<TestServerClient> TestServerClient::Create() {
+    std::string serviceName = GetUniqueServiceName();
+
+    pid_t childPid = g_remoteTestServerHostHolder->CreateTestServerOrDie(serviceName);
+    ALOGD("Created child server %s with pid %d", serviceName.c_str(), childPid);
+
+    sp<libgui_test_server::ITestServer> server =
+            waitForService<libgui_test_server::ITestServer>(String16(serviceName.c_str()));
+    LOG_ALWAYS_FATAL_IF(server == nullptr);
+    ALOGD("Created connected to child server %s", serviceName.c_str());
+
+    return sp<TestServerClient>::make(server);
+}
+
+TestServerClient::TestServerClient(const sp<libgui_test_server::ITestServer>& server)
+      : mServer(server) {}
+
+TestServerClient::~TestServerClient() {
+    Kill();
+}
+
+sp<IGraphicBufferProducer> TestServerClient::CreateProducer() {
+    std::lock_guard<std::mutex> lock(mMutex);
+
+    if (!mIsAlive) {
+        return nullptr;
+    }
+
+    view::Surface surface;
+    binder::Status status = mServer->createProducer(&surface);
+
+    if (!status.isOk()) {
+        ALOGE("Failed to create remote producer. Error: %s", status.exceptionMessage().c_str());
+        return nullptr;
+    }
+
+    if (!surface.graphicBufferProducer) {
+        ALOGE("Remote producer returned no IGBP.");
+        return nullptr;
+    }
+
+    return surface.graphicBufferProducer;
+}
+
+status_t TestServerClient::Kill() {
+    std::lock_guard<std::mutex> lock(mMutex);
+    if (!mIsAlive) {
+        return DEAD_OBJECT;
+    }
+
+    mServer->killNow();
+    mServer = nullptr;
+    mIsAlive = false;
+
+    return OK;
+}
+
+} // namespace android
diff --git a/libs/gui/tests/testserver/TestServerClient.h b/libs/gui/tests/testserver/TestServerClient.h
new file mode 100644
index 0000000..5329634
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServerClient.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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 <libgui_test_server/ITestServer.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class TestServerClient : public RefBase {
+public:
+    static void InitializeOrDie(const char* filename);
+    static sp<TestServerClient> Create();
+
+    TestServerClient(const sp<libgui_test_server::ITestServer>& server);
+    virtual ~TestServerClient() override;
+
+    sp<IGraphicBufferProducer> CreateProducer();
+    status_t Kill();
+
+private:
+    std::mutex mMutex;
+
+    sp<libgui_test_server::ITestServer> mServer;
+    bool mIsAlive = true;
+};
+
+} // namespace android
diff --git a/libs/gui/tests/testserver/TestServerCommon.h b/libs/gui/tests/testserver/TestServerCommon.h
new file mode 100644
index 0000000..7370f20
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServerCommon.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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 <fcntl.h>
+
+namespace android {
+
+/*
+ * Test -> TestServerHost Request to create a new ITestServer fork.
+ */
+struct CreateServerRequest {
+    /*
+     * Service name for new ITestServer.
+     */
+    char name[128];
+};
+
+/*
+ * TestServerHost -> Test Response for creating an ITestServer fork.
+ */
+struct CreateServerResponse {
+    /*
+     * pid of new ITestServer.
+     */
+    pid_t pid;
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/testserver/TestServerHost.cpp b/libs/gui/tests/testserver/TestServerHost.cpp
new file mode 100644
index 0000000..696c3b9
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServerHost.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TestServerHost"
+
+#include <android-base/unique_fd.h>
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/Status.h>
+#include <gui/BufferQueue.h>
+#include <gui/IConsumerListener.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <libgui_test_server/BnTestServer.h>
+#include <log/log.h>
+#include <utils/Errors.h>
+
+#include <memory>
+#include <vector>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <cstddef>
+#include <cstdlib>
+
+#include "TestServerCommon.h"
+#include "TestServerHost.h"
+
+namespace android {
+
+namespace {
+
+pid_t ForkTestServer(const char* filename, char* name) {
+    pid_t childPid = fork();
+    LOG_ALWAYS_FATAL_IF(childPid == -1);
+
+    if (childPid != 0) {
+        return childPid;
+    }
+
+    // We forked!
+    const char* test_server_flag = "--test-server";
+    char* args[] = {
+            const_cast<char*>(filename),
+            const_cast<char*>(test_server_flag),
+            name,
+            nullptr,
+    };
+
+    int ret = execv(filename, args);
+    ALOGE("Failed to exec libgui_test as a TestServer. ret=%d errno=%d (%s)", ret, errno,
+          strerror(errno));
+    _exit(EXIT_FAILURE);
+}
+
+} // namespace
+
+int TestServerHostMain(const char* filename, base::unique_fd sendPipeFd,
+                       base::unique_fd recvPipeFd) {
+    status_t status = OK;
+    LOG_ALWAYS_FATAL_IF(sizeof(status) != write(sendPipeFd.get(), &status, sizeof(status)));
+
+    ALOGE("Launched TestServerHost");
+
+    while (true) {
+        CreateServerRequest request = {};
+        ssize_t bytes = read(recvPipeFd.get(), &request, sizeof(request));
+        LOG_ALWAYS_FATAL_IF(bytes != sizeof(request));
+        pid_t childPid = ForkTestServer(filename, request.name);
+
+        CreateServerResponse response = {};
+        response.pid = childPid;
+        bytes = write(sendPipeFd.get(), &response, sizeof(response));
+        LOG_ALWAYS_FATAL_IF(bytes != sizeof(response));
+    }
+
+    return 0;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/gui/tests/testserver/TestServerHost.h b/libs/gui/tests/testserver/TestServerHost.h
new file mode 100644
index 0000000..df22c0c
--- /dev/null
+++ b/libs/gui/tests/testserver/TestServerHost.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+
+#include <string>
+
+namespace android {
+
+/*
+ * Main method for a host process for TestServers.
+ *
+ * This must be called without any binder setup having been done, because you can't fork and do
+ * binder things once ProcessState is set up.
+ * @param filename File name of this binary / the binary to execve into
+ * @param sendPipeFd Pipe FD to send data to.
+ * @param recvPipeFd Pipe FD to receive data from.
+ * @return retcode
+ */
+int TestServerHostMain(const char* filename, base::unique_fd sendPipeFd,
+                       base::unique_fd recvPipeFd);
+
+} // namespace android
diff --git a/libs/gui/tests/testserver/aidl/libgui_test_server/ITestServer.aidl b/libs/gui/tests/testserver/aidl/libgui_test_server/ITestServer.aidl
new file mode 100644
index 0000000..c939ea0
--- /dev/null
+++ b/libs/gui/tests/testserver/aidl/libgui_test_server/ITestServer.aidl
@@ -0,0 +1,12 @@
+package libgui_test_server;
+
+import android.view.Surface;
+
+// Test server for libgui_test
+interface ITestServer {
+    // Create a new producer. The server will have connected to the consumer.
+    Surface createProducer();
+
+    // Kills the server immediately.
+    void killNow();
+}
diff --git a/libs/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 7c15e7c..84c2a6a 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -121,5 +121,11 @@
     return str.value_or(String16());
 }
 
+std::string Surface::toString() const {
+    std::stringstream out;
+    out << name;
+    return out.str();
+}
+
 } // namespace view
 } // namespace android
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 2b5d375..35d704a 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -30,6 +30,8 @@
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
         "android/os/InputConfig.aidl",
+        "android/os/MotionEventFlag.aidl",
+        "android/os/PointerIconType.aidl",
     ],
 }
 
@@ -84,11 +86,6 @@
 
     bindgen_flags: [
         "--verbose",
-        "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
-        "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
-        "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
         "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
         "--allowlist-var=AMOTION_EVENT_ACTION_UP",
         "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
@@ -117,6 +114,27 @@
         "--allowlist-var=AINPUT_SOURCE_HDMI",
         "--allowlist-var=AINPUT_SOURCE_SENSOR",
         "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_NONE",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC",
+        "--allowlist-var=AINPUT_KEYBOARD_TYPE_ALPHABETIC",
+        "--allowlist-var=AMETA_NONE",
+        "--allowlist-var=AMETA_ALT_ON",
+        "--allowlist-var=AMETA_ALT_LEFT_ON",
+        "--allowlist-var=AMETA_ALT_RIGHT_ON",
+        "--allowlist-var=AMETA_SHIFT_ON",
+        "--allowlist-var=AMETA_SHIFT_LEFT_ON",
+        "--allowlist-var=AMETA_SHIFT_RIGHT_ON",
+        "--allowlist-var=AMETA_SYM_ON",
+        "--allowlist-var=AMETA_FUNCTION_ON",
+        "--allowlist-var=AMETA_CTRL_ON",
+        "--allowlist-var=AMETA_CTRL_LEFT_ON",
+        "--allowlist-var=AMETA_CTRL_RIGHT_ON",
+        "--allowlist-var=AMETA_META_ON",
+        "--allowlist-var=AMETA_META_LEFT_ON",
+        "--allowlist-var=AMETA_META_RIGHT_ON",
+        "--allowlist-var=AMETA_CAPS_LOCK_ON",
+        "--allowlist-var=AMETA_NUM_LOCK_ON",
+        "--allowlist-var=AMETA_SCROLL_LOCK_ON",
     ],
 
     static_libs: [
@@ -131,6 +149,29 @@
     ],
 }
 
+cc_library_static {
+    name: "iinputflinger_aidl_lib_static",
+    host_supported: true,
+    srcs: [
+        "android/os/IInputFlinger.aidl",
+        "android/os/InputChannelCore.aidl",
+    ],
+    shared_libs: [
+        "libbinder",
+    ],
+    whole_static_libs: [
+        "libgui_window_info_static",
+    ],
+    aidl: {
+        export_aidl_headers: true,
+        local_include_dirs: ["."],
+        include_dirs: [
+            "frameworks/native/libs/gui",
+            "frameworks/native/libs/input",
+        ],
+    },
+}
+
 // Contains methods to help access C++ code from rust
 cc_library_static {
     name: "libinput_from_rust_to_cpp",
@@ -175,21 +216,23 @@
         "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
     ],
     srcs: [
-        "android/os/IInputFlinger.aidl",
-        "android/os/InputChannelCore.aidl",
         "AccelerationCurve.cpp",
         "Input.cpp",
+        "InputConsumer.cpp",
+        "InputConsumerNoResampling.cpp",
         "InputDevice.cpp",
         "InputEventLabels.cpp",
         "InputTransport.cpp",
         "InputVerifier.cpp",
         "Keyboard.cpp",
         "KeyCharacterMap.cpp",
+        "KeyboardClassifier.cpp",
         "KeyLayoutMap.cpp",
         "MotionPredictor.cpp",
         "MotionPredictorMetricsManager.cpp",
         "PrintTools.cpp",
         "PropertyMap.cpp",
+        "Resampler.cpp",
         "TfLiteMotionPredictor.cpp",
         "TouchVideoFrame.cpp",
         "VelocityControl.cpp",
@@ -216,8 +259,11 @@
     ],
 
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc",
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
+        "libbinder_ndk",
         "libcutils",
         "liblog",
         "libPlatformProperties",
@@ -239,7 +285,6 @@
 
     static_libs: [
         "inputconstants-cpp",
-        "libgui_window_info_static",
         "libui-types",
         "libtflite_static",
         "libkernelconfigs",
@@ -248,10 +293,10 @@
     whole_static_libs: [
         "com.android.input.flags-aconfig-cc",
         "libinput_rust_ffi",
+        "iinputflinger_aidl_lib_static",
     ],
 
     export_static_lib_headers: [
-        "libgui_window_info_static",
         "libui-types",
     ],
 
@@ -262,21 +307,14 @@
 
     target: {
         android: {
-            export_shared_lib_headers: ["libbinder"],
-
-            shared_libs: [
-                "libutils",
-                "libbinder",
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-
             required: [
                 "motion_predictor_model_prebuilt",
                 "motion_predictor_model_config",
             ],
+            static_libs: [
+                "libstatslog_libinput",
+                "libstatssocket_lazy",
+            ],
         },
         host: {
             include_dirs: [
@@ -285,37 +323,32 @@
             ],
         },
     },
-
-    aidl: {
-        local_include_dirs: ["."],
-        export_aidl_headers: true,
-        include_dirs: [
-            "frameworks/native/libs/gui",
-        ],
-    },
 }
 
-// Use bootstrap version of stats logging library.
-// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal
-// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot.
-cc_library {
+cc_library_static {
     name: "libstatslog_libinput",
     generated_sources: ["statslog_libinput.cpp"],
     generated_headers: ["statslog_libinput.h"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
     export_generated_headers: ["statslog_libinput.h"],
     shared_libs: [
-        "libbinder",
-        "libstatsbootstrap",
+        "libcutils",
+        "liblog",
         "libutils",
-        "android.os.statsbootstrap_aidl-cpp",
+    ],
+    static_libs: [
+        "libstatssocket_lazy",
     ],
 }
 
 genrule {
     name: "statslog_libinput.h",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" +
-        " --namespace android,stats,libinput --bootstrap",
+    cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h " +
+        "--module libinput --namespace android,libinput",
     out: [
         "statslog_libinput.h",
     ],
@@ -324,9 +357,9 @@
 genrule {
     name: "statslog_libinput.cpp",
     tools: ["stats-log-api-gen"],
-    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" +
-        " --namespace android,stats,libinput --importHeader statslog_libinput.h" +
-        " --bootstrap",
+    cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp " +
+        "--module libinput --namespace android,libinput " +
+        "--importHeader statslog_libinput.h",
     out: [
         "statslog_libinput.cpp",
     ],
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 9e0ce1d..a2bb345 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -27,15 +27,12 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <cutils/compiler.h>
-#include <gui/constants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
-#ifdef __linux__
 #include <binder/Parcel.h>
-#endif
 #if defined(__ANDROID__)
 #include <sys/random.h>
 #endif
@@ -60,6 +57,58 @@
     return !isFromSource(source, AINPUT_SOURCE_CLASS_POINTER);
 }
 
+int32_t resolveActionForSplitMotionEvent(
+        int32_t action, int32_t flags, const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerProperties>& splitPointerProperties) {
+    LOG_ALWAYS_FATAL_IF(splitPointerProperties.empty());
+    const auto maskedAction = MotionEvent::getActionMasked(action);
+    if (maskedAction != AMOTION_EVENT_ACTION_POINTER_DOWN &&
+        maskedAction != AMOTION_EVENT_ACTION_POINTER_UP) {
+        // The action is unaffected by splitting this motion event.
+        return action;
+    }
+    const auto actionIndex = MotionEvent::getActionIndex(action);
+    if (CC_UNLIKELY(actionIndex >= pointerProperties.size())) {
+        LOG(FATAL) << "Action index is out of bounds, index: " << actionIndex;
+    }
+
+    const auto affectedPointerId = pointerProperties[actionIndex].id;
+    std::optional<uint32_t> splitActionIndex;
+    for (uint32_t i = 0; i < splitPointerProperties.size(); i++) {
+        if (affectedPointerId == splitPointerProperties[i].id) {
+            splitActionIndex = i;
+            break;
+        }
+    }
+    if (!splitActionIndex.has_value()) {
+        // The affected pointer is not part of the split motion event.
+        return AMOTION_EVENT_ACTION_MOVE;
+    }
+
+    if (splitPointerProperties.size() > 1) {
+        return maskedAction | (*splitActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+    }
+
+    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+        return ((flags & AMOTION_EVENT_FLAG_CANCELED) != 0) ? AMOTION_EVENT_ACTION_CANCEL
+                                                            : AMOTION_EVENT_ACTION_UP;
+    }
+    return AMOTION_EVENT_ACTION_DOWN;
+}
+
+float transformOrientation(const ui::Transform& transform, const PointerCoords& coords,
+                           int32_t motionEventFlags) {
+    if ((motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION) == 0) {
+        return 0;
+    }
+
+    const bool isDirectionalAngle =
+            (motionEventFlags & AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION) != 0;
+
+    return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
+                          isDirectionalAngle);
+}
+
 } // namespace
 
 const char* motionClassificationToString(MotionClassification classification) {
@@ -151,7 +200,7 @@
     return roundTransformedCoords(transformedXy - transformedOrigin);
 }
 
-float transformAngle(const ui::Transform& transform, float angleRadians) {
+float transformAngle(const ui::Transform& transform, float angleRadians, bool isDirectional) {
     // Construct and transform a vector oriented at the specified clockwise angle from vertical.
     // Coordinate system: down is increasing Y, right is increasing X.
     float x = sinf(angleRadians);
@@ -165,6 +214,11 @@
     transformedPoint.x -= origin.x;
     transformedPoint.y -= origin.y;
 
+    if (!isDirectional && transformedPoint.y > 0) {
+        // Limit the range of atan2f to [-pi/2, pi/2] by reversing the direction of the vector.
+        transformedPoint *= -1;
+    }
+
     // Derive the transformed vector's clockwise angle from vertical.
     // The return value of atan2f is in range [-pi, pi] which conforms to the orientation API.
     return atan2f(transformedPoint.x, -transformedPoint.y);
@@ -254,8 +308,8 @@
             event.getButtonState()};
 }
 
-void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                            std::array<uint8_t, 32> hmac) {
+void InputEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                            ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac) {
     mId = id;
     mDeviceId = deviceId;
     mSource = source;
@@ -317,10 +371,11 @@
     return InputEventLookup::getKeyCodeByLabel(label);
 }
 
-void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                          std::array<uint8_t, 32> hmac, int32_t action, int32_t flags,
-                          int32_t keyCode, int32_t scanCode, int32_t metaState, int32_t repeatCount,
-                          nsecs_t downTime, nsecs_t eventTime) {
+void KeyEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                          ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                          int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
+                          nsecs_t eventTime) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
     mAction = action;
     mFlags = flags;
@@ -444,7 +499,6 @@
     scaleAxisValue(*this, AMOTION_EVENT_AXIS_RELATIVE_Y, windowYScale);
 }
 
-#ifdef __linux__
 status_t PointerCoords::readFromParcel(Parcel* parcel) {
     bits = parcel->readInt64();
 
@@ -472,7 +526,6 @@
     parcel->writeBool(isResampled);
     return OK;
 }
-#endif
 
 void PointerCoords::tooManyAxes(int axis) {
     ALOGW("Could not set value for axis %d because the PointerCoords structure is full and "
@@ -495,36 +548,17 @@
     return true;
 }
 
-void PointerCoords::transform(const ui::Transform& transform) {
-    const vec2 xy = transform.transform(getXYValue());
-    setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
-    setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
-
-    if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_X) ||
-        BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_RELATIVE_Y)) {
-        const ui::Transform rotation(transform.getOrientation());
-        const vec2 relativeXy = rotation.transform(getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
-                                                   getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
-        setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
-        setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
-    }
-
-    if (BitSet64::hasBit(bits, AMOTION_EVENT_AXIS_ORIENTATION)) {
-        const float val = getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
-        setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, transformAngle(transform, val));
-    }
-}
-
 // --- MotionEvent ---
 
-void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
-                             std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
-                             int32_t flags, int32_t edgeFlags, int32_t metaState,
-                             int32_t buttonState, MotionClassification classification,
-                             const ui::Transform& transform, float xPrecision, float yPrecision,
-                             float rawXCursorPosition, float rawYCursorPosition,
-                             const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
-                             size_t pointerCount, const PointerProperties* pointerProperties,
+void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source,
+                             ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac,
+                             int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+                             int32_t metaState, int32_t buttonState,
+                             MotionClassification classification, const ui::Transform& transform,
+                             float xPrecision, float yPrecision, float rawXCursorPosition,
+                             float rawYCursorPosition, const ui::Transform& rawTransform,
+                             nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                             const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
     mAction = action;
@@ -546,7 +580,7 @@
                               &pointerProperties[pointerCount]);
     mSampleEventTimes.clear();
     mSamplePointerCoords.clear();
-    addSample(eventTime, pointerCoords);
+    addSample(eventTime, pointerCoords, mId);
 }
 
 void MotionEvent::copyFrom(const MotionEvent* other, bool keepHistory) {
@@ -584,9 +618,31 @@
     }
 }
 
-void MotionEvent::addSample(
-        int64_t eventTime,
-        const PointerCoords* pointerCoords) {
+void MotionEvent::splitFrom(const android::MotionEvent& other,
+                            std::bitset<MAX_POINTER_ID + 1> splitPointerIds, int32_t newEventId) {
+    // TODO(b/327503168): The down time should be a parameter to the split function, because only
+    //   the caller can know when the first event went down on the target.
+    const nsecs_t splitDownTime = other.mDownTime;
+
+    auto [action, pointerProperties, pointerCoords] =
+            split(other.getAction(), other.getFlags(), other.getHistorySize(),
+                  other.mPointerProperties, other.mSamplePointerCoords, splitPointerIds);
+
+    // Initialize the event with zero pointers, and manually set the split pointers.
+    initialize(newEventId, other.mDeviceId, other.mSource, other.mDisplayId, /*hmac=*/{}, action,
+               other.mActionButton, other.mFlags, other.mEdgeFlags, other.mMetaState,
+               other.mButtonState, other.mClassification, other.mTransform, other.mXPrecision,
+               other.mYPrecision, other.mRawXCursorPosition, other.mRawYCursorPosition,
+               other.mRawTransform, splitDownTime, other.getEventTime(), /*pointerCount=*/0,
+               pointerProperties.data(), pointerCoords.data());
+    mPointerProperties = std::move(pointerProperties);
+    mSamplePointerCoords = std::move(pointerCoords);
+    mSampleEventTimes = other.mSampleEventTimes;
+}
+
+void MotionEvent::addSample(int64_t eventTime, const PointerCoords* pointerCoords,
+                            int32_t eventId) {
+    mId = eventId;
     mSampleEventTimes.push_back(eventTime);
     mSamplePointerCoords.insert(mSamplePointerCoords.end(), &pointerCoords[0],
                                 &pointerCoords[getPointerCount()]);
@@ -665,13 +721,13 @@
 float MotionEvent::getHistoricalRawAxisValue(int32_t axis, size_t pointerIndex,
                                              size_t historicalIndex) const {
     const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-    return calculateTransformedAxisValue(axis, mSource, mRawTransform, coords);
+    return calculateTransformedAxisValue(axis, mSource, mFlags, mRawTransform, coords);
 }
 
 float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
                                           size_t historicalIndex) const {
     const PointerCoords& coords = *getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
-    return calculateTransformedAxisValue(axis, mSource, mTransform, coords);
+    return calculateTransformedAxisValue(axis, mSource, mFlags, mTransform, coords);
 }
 
 ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
@@ -690,6 +746,18 @@
     mTransform.set(currXOffset + xOffset, currYOffset + yOffset);
 }
 
+float MotionEvent::getRawXOffset() const {
+    // This is equivalent to the x-coordinate of the point that the origin of the raw coordinate
+    // space maps to.
+    return (mTransform * mRawTransform.inverse()).tx();
+}
+
+float MotionEvent::getRawYOffset() const {
+    // This is equivalent to the y-coordinate of the point that the origin of the raw coordinate
+    // space maps to.
+    return (mTransform * mRawTransform.inverse()).ty();
+}
+
 void MotionEvent::scale(float globalScaleFactor) {
     mTransform.set(mTransform.tx() * globalScaleFactor, mTransform.ty() * globalScaleFactor);
     mRawTransform.set(mRawTransform.tx() * globalScaleFactor,
@@ -716,8 +784,9 @@
     transform.set(matrix);
 
     // Apply the transformation to all samples.
-    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(),
-                  [&transform](PointerCoords& c) { c.transform(transform); });
+    std::for_each(mSamplePointerCoords.begin(), mSamplePointerCoords.end(), [&](PointerCoords& c) {
+        calculateTransformedCoordsInPlace(c, mSource, mFlags, transform);
+    });
 
     if (mRawXCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION &&
         mRawYCursorPosition != AMOTION_EVENT_INVALID_CURSOR_POSITION) {
@@ -727,7 +796,6 @@
     }
 }
 
-#ifdef __linux__
 static status_t readFromParcel(ui::Transform& transform, const Parcel& parcel) {
     float dsdx, dtdx, tx, dtdy, dsdy, ty;
     status_t status = parcel.readFloat(&dsdx);
@@ -762,7 +830,7 @@
     mId = parcel->readInt32();
     mDeviceId = parcel->readInt32();
     mSource = parcel->readUint32();
-    mDisplayId = parcel->readInt32();
+    mDisplayId = ui::LogicalDisplayId{parcel->readInt32()};
     std::vector<uint8_t> hmac;
     status_t result = parcel->readByteVector(&hmac);
     if (result != OK || hmac.size() != 32) {
@@ -830,7 +898,7 @@
     parcel->writeInt32(mId);
     parcel->writeInt32(mDeviceId);
     parcel->writeUint32(mSource);
-    parcel->writeInt32(mDisplayId);
+    parcel->writeInt32(mDisplayId.val());
     std::vector<uint8_t> hmac(mHmac.begin(), mHmac.end());
     parcel->writeByteVector(hmac);
     parcel->writeInt32(mAction);
@@ -874,7 +942,6 @@
     }
     return OK;
 }
-#endif
 
 bool MotionEvent::isTouchEvent(uint32_t source, int32_t action) {
     if (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER)) {
@@ -934,6 +1001,46 @@
     return android::base::StringPrintf("%" PRId32, action);
 }
 
+std::tuple<int32_t, std::vector<PointerProperties>, std::vector<PointerCoords>> MotionEvent::split(
+        int32_t action, int32_t flags, int32_t historySize,
+        const std::vector<PointerProperties>& pointerProperties,
+        const std::vector<PointerCoords>& pointerCoords,
+        std::bitset<MAX_POINTER_ID + 1> splitPointerIds) {
+    LOG_ALWAYS_FATAL_IF(!splitPointerIds.any());
+    const auto pointerCount = pointerProperties.size();
+    LOG_ALWAYS_FATAL_IF(pointerCoords.size() != (pointerCount * (historySize + 1)));
+    const auto splitCount = splitPointerIds.count();
+
+    std::vector<PointerProperties> splitPointerProperties;
+    std::vector<PointerCoords> splitPointerCoords;
+
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        if (splitPointerIds.test(pointerProperties[i].id)) {
+            splitPointerProperties.emplace_back(pointerProperties[i]);
+        }
+    }
+    for (uint32_t i = 0; i < pointerCoords.size(); i++) {
+        if (splitPointerIds.test(pointerProperties[i % pointerCount].id)) {
+            splitPointerCoords.emplace_back(pointerCoords[i]);
+        }
+    }
+    LOG_ALWAYS_FATAL_IF(splitPointerCoords.size() !=
+                        (splitPointerProperties.size() * (historySize + 1)));
+
+    if (CC_UNLIKELY(splitPointerProperties.size() != splitCount)) {
+        // TODO(b/329107108): Promote this to a fatal check once bugs in the caller are resolved.
+        LOG(ERROR) << "Cannot split MotionEvent: Requested splitting " << splitCount
+                   << " pointers from the original event, but the original event only contained "
+                   << splitPointerProperties.size() << " of those pointers.";
+    }
+
+    // TODO(b/327503168): Verify the splitDownTime here once it is used correctly.
+
+    const auto splitAction = resolveActionForSplitMotionEvent(action, flags, pointerProperties,
+                                                              splitPointerProperties);
+    return {splitAction, splitPointerProperties, splitPointerCoords};
+}
+
 // Apply the given transformation to the point without checking whether the entire transform
 // should be disregarded altogether for the provided source.
 static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
@@ -951,7 +1058,7 @@
 }
 
 // Keep in sync with calculateTransformedCoords.
-float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source,
+float MotionEvent::calculateTransformedAxisValue(int32_t axis, uint32_t source, int32_t flags,
                                                  const ui::Transform& transform,
                                                  const PointerCoords& coords) {
     if (shouldDisregardTransformation(source)) {
@@ -973,7 +1080,7 @@
     }
 
     if (axis == AMOTION_EVENT_AXIS_ORIENTATION) {
-        return transformAngle(transform, coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+        return transformOrientation(transform, coords, flags);
     }
 
     return coords.getAxisValue(axis);
@@ -981,29 +1088,32 @@
 
 // Keep in sync with calculateTransformedAxisValue. This is an optimization of
 // calculateTransformedAxisValue for all PointerCoords axes.
-PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source,
-                                                      const ui::Transform& transform,
-                                                      const PointerCoords& coords) {
+void MotionEvent::calculateTransformedCoordsInPlace(PointerCoords& coords, uint32_t source,
+                                                    int32_t flags, const ui::Transform& transform) {
     if (shouldDisregardTransformation(source)) {
-        return coords;
+        return;
     }
-    PointerCoords out = coords;
 
     const vec2 xy = calculateTransformedXYUnchecked(source, transform, coords.getXYValue());
-    out.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
-    out.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, xy.x);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, xy.y);
 
     const vec2 relativeXy =
             transformWithoutTranslation(transform,
                                         {coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
                                          coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y)});
-    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
-    out.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, relativeXy.x);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, relativeXy.y);
 
-    out.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
-                     transformAngle(transform,
-                                    coords.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION)));
+    coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+                        transformOrientation(transform, coords, flags));
+}
 
+PointerCoords MotionEvent::calculateTransformedCoords(uint32_t source, int32_t flags,
+                                                      const ui::Transform& transform,
+                                                      const PointerCoords& coords) {
+    PointerCoords out = coords;
+    calculateTransformedCoordsInPlace(out, source, flags, transform);
     return out;
 }
 
@@ -1090,7 +1200,7 @@
 
 void FocusEvent::initialize(int32_t id, bool hasFocus) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mHasFocus = hasFocus;
 }
 
@@ -1103,7 +1213,7 @@
 
 void CaptureEvent::initialize(int32_t id, bool pointerCaptureEnabled) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mPointerCaptureEnabled = pointerCaptureEnabled;
 }
 
@@ -1116,7 +1226,7 @@
 
 void DragEvent::initialize(int32_t id, float x, float y, bool isExiting) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mIsExiting = isExiting;
     mX = x;
     mY = y;
@@ -1133,7 +1243,7 @@
 
 void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) {
     InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
-                           ADISPLAY_ID_NONE, INVALID_HMAC);
+                           ui::LogicalDisplayId::INVALID, INVALID_HMAC);
     mIsInTouchMode = isInTouchMode;
 }
 
diff --git a/libs/input/InputConsumer.cpp b/libs/input/InputConsumer.cpp
new file mode 100644
index 0000000..1eeb4e6
--- /dev/null
+++ b/libs/input/InputConsumer.cpp
@@ -0,0 +1,963 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdint>
+#define LOG_TAG "InputTransport"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <math.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <binder/Parcel.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumer.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+/**
+ * Log debug messages about touch event resampling.
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugResampling() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_RESAMPLING =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_RESAMPLING;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                     ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
+                     msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
+                     msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
+                     msg.body.key.downTime, msg.body.key.eventTime);
+}
+
+void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+}
+
+void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+}
+
+void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                     msg.body.drag.isExiting);
+}
+
+void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i] = msg.body.motion.pointers[i].properties;
+        pointerCoords[i] = msg.body.motion.pointers[i].coords;
+    }
+
+    ui::Transform transform;
+    transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+                   msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+                          msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+                          0, 0, 1});
+    event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                     ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
+                     msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
+                     msg.body.motion.edgeFlags, msg.body.motion.metaState,
+                     msg.body.motion.buttonState, msg.body.motion.classification, transform,
+                     msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+                     msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+                     displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+                     pointerCount, pointerProperties, pointerCoords);
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    PointerCoords pointerCoords[pointerCount];
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerCoords[i] = msg.body.motion.pointers[i].coords;
+    }
+
+    event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+    event.addSample(msg.body.motion.eventTime, pointerCoords, msg.body.motion.eventId);
+}
+
+void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
+    event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+}
+
+// Nanoseconds per milliseconds.
+constexpr nsecs_t NANOS_PER_MS = 1000000;
+
+// Latency added during resampling.  A few milliseconds doesn't hurt much but
+// reduces the impact of mispredicted touch positions.
+const std::chrono::duration RESAMPLE_LATENCY = 5ms;
+
+// Minimum time difference between consecutive samples before attempting to resample.
+const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
+
+// Maximum time difference between consecutive samples before attempting to resample
+// by extrapolation.
+const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
+
+// Maximum time to predict forward from the last known state, to avoid predicting too
+// far into the future.  This time is further bounded by 50% of the last time delta.
+const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
+
+/**
+ * System property for enabling / disabling touch resampling.
+ * Resampling extrapolates / interpolates the reported touch event coordinates to better
+ * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
+ * Resampling is not needed (and should be disabled) on hardware that already
+ * has touch events triggered by VSYNC.
+ * Set to "1" to enable resampling (default).
+ * Set to "0" to disable resampling.
+ * Resampling is enabled by default.
+ */
+const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+
+inline float lerp(float a, float b, float alpha) {
+    return a + alpha * (b - a);
+}
+
+inline bool isPointerEvent(int32_t source) {
+    return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
+
+bool shouldResampleTool(ToolType toolType) {
+    return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
+            toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
+}
+
+} // namespace
+
+using android::base::Result;
+using android::base::StringPrintf;
+
+// --- InputConsumer ---
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
+      : InputConsumer(channel, isTouchResamplingEnabled()) {}
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
+                             bool enableTouchResampling)
+      : mResampleTouch(enableTouchResampling),
+        mChannel(channel),
+        mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)",
+                                         mChannel->getName().c_str(), this)),
+        mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)",
+                                       mChannel->getName().c_str(), this)),
+        mLifetimeTraceCookie(
+                static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)),
+        mMsgDeferred(false) {
+    ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
+
+InputConsumer::~InputConsumer() {
+    ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
+}
+
+bool InputConsumer::isTouchResamplingEnabled() {
+    return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
+}
+
+status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
+                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+             mChannel->getName().c_str(), toString(consumeBatches), frameTime);
+
+    *outSeq = 0;
+    *outEvent = nullptr;
+
+    // Fetch the next input message.
+    // Loop until an event can be returned or no additional events are received.
+    while (!*outEvent) {
+        if (mMsgDeferred) {
+            // mMsg contains a valid input message from the previous call to consume
+            // that has not yet been processed.
+            mMsgDeferred = false;
+        } else {
+            // Receive a fresh message.
+            android::base::Result<InputMessage> result = mChannel->receiveMessage();
+            if (result.ok()) {
+                mMsg = std::move(result.value());
+                const auto [_, inserted] =
+                        mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                    mMsg.header.seq);
+
+                // Trace the event processing timeline - event was just read from the socket
+                ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
+            } else {
+                // Consume the next batched event unless batches are being held for later.
+                if (consumeBatches || result.error().code() != WOULD_BLOCK) {
+                    result = android::base::Error(
+                            consumeBatch(factory, frameTime, outSeq, outEvent));
+                    if (*outEvent) {
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
+                        break;
+                    }
+                }
+                return result.error().code();
+            }
+        }
+
+        switch (mMsg.header.type) {
+            case InputMessage::Type::KEY: {
+                KeyEvent* keyEvent = factory->createKeyEvent();
+                if (!keyEvent) return NO_MEMORY;
+
+                initializeKeyEvent(*keyEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = keyEvent;
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed key event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
+                break;
+            }
+
+            case InputMessage::Type::MOTION: {
+                ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
+                if (batchIndex >= 0) {
+                    Batch& batch = mBatches[batchIndex];
+                    if (canAddSample(batch, &mMsg)) {
+                        batch.samples.push_back(mMsg);
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ appended to batch event",
+                                 mChannel->getName().c_str());
+                        break;
+                    } else if (isPointerEvent(mMsg.body.motion.source) &&
+                               mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
+                        // No need to process events that we are going to cancel anyways
+                        const size_t count = batch.samples.size();
+                        for (size_t i = 0; i < count; i++) {
+                            const InputMessage& msg = batch.samples[i];
+                            sendFinishedSignal(msg.header.seq, false);
+                        }
+                        batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+                        mBatches.erase(mBatches.begin() + batchIndex);
+                    } else {
+                        // We cannot append to the batch in progress, so we need to consume
+                        // the previous batch right now and defer the new message until later.
+                        mMsgDeferred = true;
+                        status_t result = consumeSamples(factory, batch, batch.samples.size(),
+                                                         outSeq, outEvent);
+                        mBatches.erase(mBatches.begin() + batchIndex);
+                        if (result) {
+                            return result;
+                        }
+                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                                 "channel '%s' consumer ~ consumed batch event and "
+                                 "deferred current event, seq=%u",
+                                 mChannel->getName().c_str(), *outSeq);
+                        break;
+                    }
+                }
+
+                // Start a new batch if needed.
+                if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
+                    mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+                    Batch batch;
+                    batch.samples.push_back(mMsg);
+                    mBatches.push_back(batch);
+                    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                             "channel '%s' consumer ~ started batch event",
+                             mChannel->getName().c_str());
+                    break;
+                }
+
+                MotionEvent* motionEvent = factory->createMotionEvent();
+                if (!motionEvent) return NO_MEMORY;
+
+                updateTouchState(mMsg);
+                initializeMotionEvent(*motionEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = motionEvent;
+
+                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+                         "channel '%s' consumer ~ consumed motion event, seq=%u",
+                         mChannel->getName().c_str(), *outSeq);
+                break;
+            }
+
+            case InputMessage::Type::FINISHED:
+            case InputMessage::Type::TIMELINE: {
+                LOG(FATAL) << "Consumed a " << ftl::enum_string(mMsg.header.type)
+                           << " message, which should never be seen by "
+                              "InputConsumer on "
+                           << mChannel->getName();
+                break;
+            }
+
+            case InputMessage::Type::FOCUS: {
+                FocusEvent* focusEvent = factory->createFocusEvent();
+                if (!focusEvent) return NO_MEMORY;
+
+                initializeFocusEvent(*focusEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = focusEvent;
+                break;
+            }
+
+            case InputMessage::Type::CAPTURE: {
+                CaptureEvent* captureEvent = factory->createCaptureEvent();
+                if (!captureEvent) return NO_MEMORY;
+
+                initializeCaptureEvent(*captureEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = captureEvent;
+                break;
+            }
+
+            case InputMessage::Type::DRAG: {
+                DragEvent* dragEvent = factory->createDragEvent();
+                if (!dragEvent) return NO_MEMORY;
+
+                initializeDragEvent(*dragEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = dragEvent;
+                break;
+            }
+
+            case InputMessage::Type::TOUCH_MODE: {
+                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
+                if (!touchModeEvent) return NO_MEMORY;
+
+                initializeTouchModeEvent(*touchModeEvent, mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = touchModeEvent;
+                break;
+            }
+        }
+    }
+    return OK;
+}
+
+status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime,
+                                     uint32_t* outSeq, InputEvent** outEvent) {
+    status_t result;
+    for (size_t i = mBatches.size(); i > 0;) {
+        i--;
+        Batch& batch = mBatches[i];
+        if (frameTime < 0) {
+            result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
+            mBatches.erase(mBatches.begin() + i);
+            return result;
+        }
+
+        nsecs_t sampleTime = frameTime;
+        if (mResampleTouch) {
+            sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
+        }
+        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
+        if (split < 0) {
+            continue;
+        }
+
+        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
+        const InputMessage* next;
+        if (batch.samples.empty()) {
+            mBatches.erase(mBatches.begin() + i);
+            next = nullptr;
+        } else {
+            next = &batch.samples[0];
+        }
+        if (!result && mResampleTouch) {
+            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
+        }
+        return result;
+    }
+
+    return WOULD_BLOCK;
+}
+
+status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, Batch& batch,
+                                       size_t count, uint32_t* outSeq, InputEvent** outEvent) {
+    MotionEvent* motionEvent = factory->createMotionEvent();
+    if (!motionEvent) return NO_MEMORY;
+
+    uint32_t chain = 0;
+    for (size_t i = 0; i < count; i++) {
+        InputMessage& msg = batch.samples[i];
+        updateTouchState(msg);
+        if (i) {
+            SeqChain seqChain;
+            seqChain.seq = msg.header.seq;
+            seqChain.chain = chain;
+            mSeqChains.push_back(seqChain);
+            addSample(*motionEvent, msg);
+        } else {
+            initializeMotionEvent(*motionEvent, msg);
+        }
+        chain = msg.header.seq;
+    }
+    batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
+
+    *outSeq = chain;
+    *outEvent = motionEvent;
+    return OK;
+}
+
+void InputConsumer::updateTouchState(InputMessage& msg) {
+    if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
+        return;
+    }
+
+    int32_t deviceId = msg.body.motion.deviceId;
+    int32_t source = msg.body.motion.source;
+
+    // Update the touch state history to incorporate the new input message.
+    // If the message is in the past relative to the most recently produced resampled
+    // touch, then use the resampled time and coordinates instead.
+    switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
+        case AMOTION_EVENT_ACTION_DOWN: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index < 0) {
+                mTouchStates.push_back({});
+                index = mTouchStates.size() - 1;
+            }
+            TouchState& touchState = mTouchStates[index];
+            touchState.initialize(deviceId, source);
+            touchState.addHistory(msg);
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_MOVE: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                touchState.addHistory(msg);
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_POINTER_UP: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+                touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_SCROLL: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+            }
+            break;
+        }
+
+        case AMOTION_EVENT_ACTION_UP:
+        case AMOTION_EVENT_ACTION_CANCEL: {
+            ssize_t index = findTouchState(deviceId, source);
+            if (index >= 0) {
+                TouchState& touchState = mTouchStates[index];
+                rewriteMessage(touchState, msg);
+                mTouchStates.erase(mTouchStates.begin() + index);
+            }
+            break;
+        }
+    }
+}
+
+/**
+ * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
+ *
+ * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
+ * is in the past relative to msg and the past two events do not contain identical coordinates),
+ * then invalidate the lastResample data for that pointer.
+ * If the two past events have identical coordinates, then lastResample data for that pointer will
+ * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
+ * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
+ * not equal to x0 is received.
+ */
+void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
+    nsecs_t eventTime = msg.body.motion.eventTime;
+    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+        uint32_t id = msg.body.motion.pointers[i].properties.id;
+        if (state.lastResample.idBits.hasBit(id)) {
+            if (eventTime < state.lastResample.eventTime ||
+                state.recentCoordinatesAreIdentical(id)) {
+                PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
+                const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
+                ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+                         resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
+                         msgCoords.getY());
+                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
+                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+                msgCoords.isResampled = true;
+            } else {
+                state.lastResample.idBits.clearBit(id);
+            }
+        }
+    }
+}
+
+void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
+                                       const InputMessage* next) {
+    if (!mResampleTouch || !(isPointerEvent(event->getSource())) ||
+        event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
+        return;
+    }
+
+    ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
+    if (index < 0) {
+        ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
+        return;
+    }
+
+    TouchState& touchState = mTouchStates[index];
+    if (touchState.historySize < 1) {
+        ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
+        return;
+    }
+
+    // Ensure that the current sample has all of the pointers that need to be reported.
+    const History* current = touchState.getHistory(0);
+    size_t pointerCount = event->getPointerCount();
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        if (!current->idBits.hasBit(id)) {
+            ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
+            return;
+        }
+        if (!shouldResampleTool(event->getToolType(i))) {
+            ALOGD_IF(debugResampling(),
+                     "Not resampled, containing unsupported tool type at pointer %d", id);
+            return;
+        }
+    }
+
+    // Find the data to use for resampling.
+    const History* other;
+    History future;
+    float alpha;
+    if (next) {
+        // Interpolate between current sample and future sample.
+        // So current->eventTime <= sampleTime <= future.eventTime.
+        future.initializeFrom(*next);
+        other = &future;
+        nsecs_t delta = future.eventTime - current->eventTime;
+        if (delta < RESAMPLE_MIN_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
+            return;
+        }
+        alpha = float(sampleTime - current->eventTime) / delta;
+    } else if (touchState.historySize >= 2) {
+        // Extrapolate future sample using current sample and past sample.
+        // So other->eventTime <= current->eventTime <= sampleTime.
+        other = touchState.getHistory(1);
+        nsecs_t delta = current->eventTime - other->eventTime;
+        if (delta < RESAMPLE_MIN_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+                     delta);
+            return;
+        } else if (delta > RESAMPLE_MAX_DELTA) {
+            ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
+                     delta);
+            return;
+        }
+        nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION);
+        if (sampleTime > maxPredict) {
+            ALOGD_IF(debugResampling(),
+                     "Sample time is too far in the future, adjusting prediction "
+                     "from %" PRId64 " to %" PRId64 " ns.",
+                     sampleTime - current->eventTime, maxPredict - current->eventTime);
+            sampleTime = maxPredict;
+        }
+        alpha = float(current->eventTime - sampleTime) / delta;
+    } else {
+        ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
+        return;
+    }
+
+    if (current->eventTime == sampleTime) {
+        ALOGD_IF(debugResampling(), "Not resampled, 2 events with identical times.");
+        return;
+    }
+
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        if (!other->idBits.hasBit(id)) {
+            ALOGD_IF(debugResampling(), "Not resampled, the other doesn't have pointer id %d.", id);
+            return;
+        }
+    }
+
+    // Resample touch coordinates.
+    History oldLastResample;
+    oldLastResample.initializeFrom(touchState.lastResample);
+    touchState.lastResample.eventTime = sampleTime;
+    touchState.lastResample.idBits.clear();
+    for (size_t i = 0; i < pointerCount; i++) {
+        uint32_t id = event->getPointerId(i);
+        touchState.lastResample.idToIndex[id] = i;
+        touchState.lastResample.idBits.markBit(id);
+        if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
+            // We maintain the previously resampled value for this pointer (stored in
+            // oldLastResample) when the coordinates for this pointer haven't changed since then.
+            // This way we don't introduce artificial jitter when pointers haven't actually moved.
+            // The isResampled flag isn't cleared as the values don't reflect what the device is
+            // actually reporting.
+
+            // We know here that the coordinates for the pointer haven't changed because we
+            // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
+            // lastResample in place because the mapping from pointer ID to index may have changed.
+            touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
+            continue;
+        }
+
+        PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
+        const PointerCoords& currentCoords = current->getPointerById(id);
+        resampledCoords = currentCoords;
+        resampledCoords.isResampled = true;
+        const PointerCoords& otherCoords = other->getPointerById(id);
+        resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
+                                     lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+        resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+                                     lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+        ALOGD_IF(debugResampling(),
+                 "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+                 "other (%0.3f, %0.3f), alpha %0.3f",
+                 id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+                 currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
+    }
+
+    event->addSample(sampleTime, touchState.lastResample.pointers, event->getId());
+}
+
+status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+             mChannel->getName().c_str(), seq, toString(handled));
+
+    if (!seq) {
+        ALOGE("Attempted to send a finished signal with sequence number 0.");
+        return BAD_VALUE;
+    }
+
+    // Send finished signals for the batch sequence chain first.
+    size_t seqChainCount = mSeqChains.size();
+    if (seqChainCount) {
+        uint32_t currentSeq = seq;
+        uint32_t chainSeqs[seqChainCount];
+        size_t chainIndex = 0;
+        for (size_t i = seqChainCount; i > 0;) {
+            i--;
+            const SeqChain& seqChain = mSeqChains[i];
+            if (seqChain.seq == currentSeq) {
+                currentSeq = seqChain.chain;
+                chainSeqs[chainIndex++] = currentSeq;
+                mSeqChains.erase(mSeqChains.begin() + i);
+            }
+        }
+        status_t status = OK;
+        while (!status && chainIndex > 0) {
+            chainIndex--;
+            status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
+        }
+        if (status) {
+            // An error occurred so at least one signal was not sent, reconstruct the chain.
+            for (;;) {
+                SeqChain seqChain;
+                seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
+                seqChain.chain = chainSeqs[chainIndex];
+                mSeqChains.push_back(seqChain);
+                if (!chainIndex) break;
+                chainIndex--;
+            }
+            return status;
+        }
+    }
+
+    // Send finished signal for the last message in the batch.
+    return sendUnchainedFinishedSignal(seq, handled);
+}
+
+status_t InputConsumer::sendTimeline(int32_t inputEventId,
+                                     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+             "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+             ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+             mChannel->getName().c_str(), inputEventId,
+             graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+             graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
+    return mChannel->sendMessage(&msg);
+}
+
+nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
+    auto it = mConsumeTimes.find(seq);
+    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+                        seq);
+    return it->second;
+}
+
+void InputConsumer::popConsumeTime(uint32_t seq) {
+    mConsumeTimes.erase(seq);
+}
+
+status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FINISHED;
+    msg.header.seq = seq;
+    msg.body.finished.handled = handled;
+    msg.body.finished.consumeTime = getConsumeTime(seq);
+    status_t result = mChannel->sendMessage(&msg);
+    if (result == OK) {
+        // Remove the consume time if the socket write succeeded. We will not need to ack this
+        // message anymore. If the socket write did not succeed, we will try again and will still
+        // need consume time.
+        popConsumeTime(seq);
+
+        // Trace the event processing timeline - event was just finished
+        ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq);
+    }
+    return result;
+}
+
+bool InputConsumer::hasPendingBatch() const {
+    return !mBatches.empty();
+}
+
+int32_t InputConsumer::getPendingBatchSource() const {
+    if (mBatches.empty()) {
+        return AINPUT_SOURCE_CLASS_NONE;
+    }
+
+    const Batch& batch = mBatches[0];
+    const InputMessage& head = batch.samples[0];
+    return head.body.motion.source;
+}
+
+bool InputConsumer::probablyHasInput() const {
+    return hasPendingBatch() || mChannel->probablyHasInput();
+}
+
+ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
+    for (size_t i = 0; i < mBatches.size(); i++) {
+        const Batch& batch = mBatches[i];
+        const InputMessage& head = batch.samples[0];
+        if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
+    for (size_t i = 0; i < mTouchStates.size(); i++) {
+        const TouchState& touchState = mTouchStates[i];
+        if (touchState.deviceId == deviceId && touchState.source == source) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+bool InputConsumer::canAddSample(const Batch& batch, const InputMessage* msg) {
+    const InputMessage& head = batch.samples[0];
+    uint32_t pointerCount = msg->body.motion.pointerCount;
+    if (head.body.motion.pointerCount != pointerCount ||
+        head.body.motion.action != msg->body.motion.action) {
+        return false;
+    }
+    for (size_t i = 0; i < pointerCount; i++) {
+        if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
+            return false;
+        }
+    }
+    return true;
+}
+
+ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
+    size_t numSamples = batch.samples.size();
+    size_t index = 0;
+    while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
+        index += 1;
+    }
+    return ssize_t(index) - 1;
+}
+
+std::string InputConsumer::dump() const {
+    std::string out;
+    out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
+    out = out + "mChannel = " + mChannel->getName() + "\n";
+    out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
+    if (mMsgDeferred) {
+        out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
+    }
+    out += "Batches:\n";
+    for (const Batch& batch : mBatches) {
+        out += "    Batch:\n";
+        for (const InputMessage& msg : batch.samples) {
+            out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
+                                               ftl::enum_string(msg.header.type).c_str());
+            switch (msg.header.type) {
+                case InputMessage::Type::KEY: {
+                    out += android::base::StringPrintf("action=%s keycode=%" PRId32,
+                                                       KeyEvent::actionToString(
+                                                               msg.body.key.action),
+                                                       msg.body.key.keyCode);
+                    break;
+                }
+                case InputMessage::Type::MOTION: {
+                    out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
+                    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
+                        const float x = msg.body.motion.pointers[i].coords.getX();
+                        const float y = msg.body.motion.pointers[i].coords.getY();
+                        out += android::base::StringPrintf("\n            Pointer %" PRIu32
+                                                           " : x=%.1f y=%.1f",
+                                                           i, x, y);
+                    }
+                    break;
+                }
+                case InputMessage::Type::FINISHED: {
+                    out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
+                                                       toString(msg.body.finished.handled),
+                                                       msg.body.finished.consumeTime);
+                    break;
+                }
+                case InputMessage::Type::FOCUS: {
+                    out += android::base::StringPrintf("hasFocus=%s",
+                                                       toString(msg.body.focus.hasFocus));
+                    break;
+                }
+                case InputMessage::Type::CAPTURE: {
+                    out += android::base::StringPrintf("hasCapture=%s",
+                                                       toString(msg.body.capture
+                                                                        .pointerCaptureEnabled));
+                    break;
+                }
+                case InputMessage::Type::DRAG: {
+                    out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
+                                                       msg.body.drag.x, msg.body.drag.y,
+                                                       toString(msg.body.drag.isExiting));
+                    break;
+                }
+                case InputMessage::Type::TIMELINE: {
+                    const nsecs_t gpuCompletedTime =
+                            msg.body.timeline
+                                    .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+                    const nsecs_t presentTime =
+                            msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+                    out += android::base::StringPrintf("inputEventId=%" PRId32
+                                                       ", gpuCompletedTime=%" PRId64
+                                                       ", presentTime=%" PRId64,
+                                                       msg.body.timeline.eventId, gpuCompletedTime,
+                                                       presentTime);
+                    break;
+                }
+                case InputMessage::Type::TOUCH_MODE: {
+                    out += android::base::StringPrintf("isInTouchMode=%s",
+                                                       toString(msg.body.touchMode.isInTouchMode));
+                    break;
+                }
+            }
+            out += "\n";
+        }
+    }
+    if (mBatches.empty()) {
+        out += "    <empty>\n";
+    }
+    out += "mSeqChains:\n";
+    for (const SeqChain& chain : mSeqChains) {
+        out += android::base::StringPrintf("    chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
+                                           chain.chain);
+    }
+    if (mSeqChains.empty()) {
+        out += "    <empty>\n";
+    }
+    out += "mConsumeTimes:\n";
+    for (const auto& [seq, consumeTime] : mConsumeTimes) {
+        out += android::base::StringPrintf("    seq = %" PRIu32 " consumeTime = %" PRId64, seq,
+                                           consumeTime);
+    }
+    if (mConsumeTimes.empty()) {
+        out += "    <empty>\n";
+    }
+    return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
new file mode 100644
index 0000000..cdbc186
--- /dev/null
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -0,0 +1,575 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "InputConsumerNoResampling"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <chrono>
+
+#include <inttypes.h>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <cutils/properties.h>
+#include <ftl/enum.h>
+#include <utils/Trace.h>
+
+#include <com_android_input_flags.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+
+namespace android {
+
+namespace {
+
+using std::chrono::nanoseconds;
+
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+const bool DEBUG_TRANSPORT_CONSUMER =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
+    std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
+    event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
+                      ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
+                      msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
+                      msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
+                      msg.body.key.downTime, msg.body.key.eventTime);
+    return event;
+}
+
+std::unique_ptr<FocusEvent> createFocusEvent(const InputMessage& msg) {
+    std::unique_ptr<FocusEvent> event = std::make_unique<FocusEvent>();
+    event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
+    return event;
+}
+
+std::unique_ptr<CaptureEvent> createCaptureEvent(const InputMessage& msg) {
+    std::unique_ptr<CaptureEvent> event = std::make_unique<CaptureEvent>();
+    event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
+    return event;
+}
+
+std::unique_ptr<DragEvent> createDragEvent(const InputMessage& msg) {
+    std::unique_ptr<DragEvent> event = std::make_unique<DragEvent>();
+    event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
+                      msg.body.drag.isExiting);
+    return event;
+}
+
+std::unique_ptr<MotionEvent> createMotionEvent(const InputMessage& msg) {
+    std::unique_ptr<MotionEvent> event = std::make_unique<MotionEvent>();
+    const uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    pointerProperties.reserve(pointerCount);
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back(msg.body.motion.pointers[i].properties);
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    ui::Transform transform;
+    transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
+                   msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
+                          msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
+                          0, 0, 1});
+    event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
+                      ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
+                      msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
+                      msg.body.motion.edgeFlags, msg.body.motion.metaState,
+                      msg.body.motion.buttonState, msg.body.motion.classification, transform,
+                      msg.body.motion.xPrecision, msg.body.motion.yPrecision,
+                      msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
+                      displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
+                      pointerCount, pointerProperties.data(), pointerCoords.data());
+    return event;
+}
+
+void addSample(MotionEvent& event, const InputMessage& msg) {
+    uint32_t pointerCount = msg.body.motion.pointerCount;
+    std::vector<PointerCoords> pointerCoords;
+    pointerCoords.reserve(pointerCount);
+    for (uint32_t i = 0; i < pointerCount; i++) {
+        pointerCoords.push_back(msg.body.motion.pointers[i].coords);
+    }
+
+    // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState
+    event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
+    event.addSample(msg.body.motion.eventTime, pointerCoords.data(), msg.body.motion.eventId);
+}
+
+std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
+    std::unique_ptr<TouchModeEvent> event = std::make_unique<TouchModeEvent>();
+    event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
+    return event;
+}
+
+std::string outboundMessageToString(const InputMessage& outboundMsg) {
+    switch (outboundMsg.header.type) {
+        case InputMessage::Type::FINISHED: {
+            return android::base::StringPrintf("  Finish: seq=%" PRIu32 " handled=%s",
+                                               outboundMsg.header.seq,
+                                               toString(outboundMsg.body.finished.handled));
+        }
+        case InputMessage::Type::TIMELINE: {
+            return android::base::
+                    StringPrintf("  Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
+                                 ", presentTime=%" PRId64,
+                                 outboundMsg.body.timeline.eventId,
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+                                 outboundMsg.body.timeline
+                                         .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+        }
+        default: {
+            LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got "
+                       << ftl::enum_string(outboundMsg.header.type);
+            return "Unreachable";
+        }
+    }
+}
+
+InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::FINISHED;
+    msg.header.seq = seq;
+    msg.body.finished.handled = handled;
+    msg.body.finished.consumeTime = consumeTime;
+    return msg;
+}
+
+InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                   nsecs_t presentTime) {
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TIMELINE;
+    msg.header.seq = 0;
+    msg.body.timeline.eventId = inputEventId;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
+    msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
+    return msg;
+}
+
+bool isPointerEvent(const MotionEvent& motionEvent) {
+    return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
+}
+} // namespace
+
+using android::base::Result;
+
+// --- InputConsumerNoResampling ---
+
+InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
+                                                     sp<Looper> looper,
+                                                     InputConsumerCallbacks& callbacks,
+                                                     std::unique_ptr<Resampler> resampler)
+      : mChannel{channel},
+        mLooper{looper},
+        mCallbacks{callbacks},
+        mResampler{std::move(resampler)},
+        mFdEvents(0) {
+    LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
+    mCallback = sp<LooperEventCallback>::make(
+            std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
+                      std::placeholders::_1));
+    // In the beginning, there are no pending outbounds events; we only care about receiving
+    // incoming data.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+InputConsumerNoResampling::~InputConsumerNoResampling() {
+    ensureCalledOnLooperThread(__func__);
+    consumeBatchedInputEvents(/*requestedFrameTime=*/std::nullopt);
+    while (!mOutboundQueue.empty()) {
+        processOutboundEvents();
+        // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
+        // so keep trying to send the events as long as they are present in the queue.
+    }
+    setFdEvents(0);
+}
+
+int InputConsumerNoResampling::handleReceiveCallback(int events) {
+    // Allowed return values of this function as documented in LooperCallback::handleEvent
+    constexpr int REMOVE_CALLBACK = 0;
+    constexpr int KEEP_CALLBACK = 1;
+
+    if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
+        // This error typically occurs when the publisher has closed the input channel
+        // as part of removing a window or finishing an IME session, in which case
+        // the consumer will soon be disposed as well.
+        if (DEBUG_TRANSPORT_CONSUMER) {
+            LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName();
+        }
+        return REMOVE_CALLBACK;
+    }
+
+    int handledEvents = 0;
+    if (events & ALOOPER_EVENT_INPUT) {
+        handleMessages(readAllMessages());
+        handledEvents |= ALOOPER_EVENT_INPUT;
+    }
+
+    if (events & ALOOPER_EVENT_OUTPUT) {
+        processOutboundEvents();
+        handledEvents |= ALOOPER_EVENT_OUTPUT;
+    }
+    if (handledEvents != events) {
+        LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events;
+    }
+    return KEEP_CALLBACK;
+}
+
+void InputConsumerNoResampling::processOutboundEvents() {
+    while (!mOutboundQueue.empty()) {
+        const InputMessage& outboundMsg = mOutboundQueue.front();
+
+        const status_t result = mChannel->sendMessage(&outboundMsg);
+        if (result == OK) {
+            if (outboundMsg.header.type == InputMessage::Type::FINISHED) {
+                ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq);
+            }
+            // Successful send. Erase the entry and keep trying to send more
+            mOutboundQueue.pop();
+            continue;
+        }
+
+        // Publisher is busy, try again later. Keep this entry (do not erase)
+        if (result == WOULD_BLOCK) {
+            setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
+            return; // try again later
+        }
+
+        // Some other error. Give up
+        LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
+                   << "'.  status=" << statusToString(result) << "(" << result << ")";
+    }
+
+    // The queue is now empty. Tell looper there's no more output to expect.
+    setFdEvents(ALOOPER_EVENT_INPUT);
+}
+
+void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq)));
+    // also produce finish events for all batches for this seq (if any)
+    const auto it = mBatchedSequenceNumbers.find(seq);
+    if (it != mBatchedSequenceNumbers.end()) {
+        for (uint32_t subSeq : it->second) {
+            mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq)));
+        }
+        mBatchedSequenceNumbers.erase(it);
+    }
+    processOutboundEvents();
+}
+
+bool InputConsumerNoResampling::probablyHasInput() const {
+    // Ideally, this would only be allowed to run on the looper thread, and in production, it will.
+    // However, for testing, it's convenient to call this while the looper thread is blocked, so
+    // we do not call ensureCalledOnLooperThread here.
+    return (!mBatches.empty()) || mChannel->probablyHasInput();
+}
+
+void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
+                                               nsecs_t presentTime) {
+    ensureCalledOnLooperThread(__func__);
+    mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime));
+    processOutboundEvents();
+}
+
+nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) {
+    auto it = mConsumeTimes.find(seq);
+    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
+    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
+    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
+                        seq);
+    nsecs_t consumeTime = it->second;
+    mConsumeTimes.erase(it);
+    return consumeTime;
+}
+
+void InputConsumerNoResampling::setFdEvents(int events) {
+    if (mFdEvents != events) {
+        mFdEvents = events;
+        if (events != 0) {
+            mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr);
+        } else {
+            mLooper->removeFd(mChannel->getFd());
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
+    // TODO(b/297226446) : add resampling
+    for (const InputMessage& msg : messages) {
+        if (msg.header.type == InputMessage::Type::MOTION) {
+            const int32_t action = msg.body.motion.action;
+            const DeviceId deviceId = msg.body.motion.deviceId;
+            const int32_t source = msg.body.motion.source;
+            const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
+                                         action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
+                    (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
+                     isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+            if (batchableEvent) {
+                // add it to batch
+                mBatches[deviceId].emplace(msg);
+            } else {
+                // consume all pending batches for this device immediately
+                consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
+                handleMessage(msg);
+            }
+        } else {
+            // Non-motion events shouldn't force the consumption of pending batched events
+            handleMessage(msg);
+        }
+    }
+    // At the end of this, if we still have pending batches, notify the receiver about it.
+
+    // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver
+    // could choose to consume all events when notified about the batch. That means that the
+    // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is
+    // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is
+    // empty, because the receiver could choose to not consume the batch immediately.
+    std::set<int32_t> pendingBatchSources;
+    for (const auto& [_, pendingMessages] : mBatches) {
+        // Assume that all messages for a given device has the same source.
+        pendingBatchSources.insert(pendingMessages.front().body.motion.source);
+    }
+    for (const int32_t source : pendingBatchSources) {
+        const bool sourceStillRemaining =
+                std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) {
+                    return pair.second.front().body.motion.source == source;
+                });
+        if (sourceStillRemaining) {
+            mCallbacks.onBatchedInputEventPending(source);
+        }
+    }
+}
+
+std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
+    std::vector<InputMessage> messages;
+    while (true) {
+        android::base::Result<InputMessage> result = mChannel->receiveMessage();
+        if (result.ok()) {
+            const InputMessage& msg = *result;
+            const auto [_, inserted] =
+                    mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+            LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+                                msg.header.seq);
+
+            // Trace the event processing timeline - event was just read from the socket
+            // TODO(b/329777420): distinguish between multiple instances of InputConsumer
+            // in the same process.
+            ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
+            messages.push_back(msg);
+        } else { // !result.ok()
+            switch (result.error().code()) {
+                case WOULD_BLOCK: {
+                    return messages;
+                }
+                case DEAD_OBJECT: {
+                    LOG(FATAL) << "Got a dead object for " << mChannel->getName();
+                    break;
+                }
+                case BAD_VALUE: {
+                    LOG(FATAL) << "Got a bad value for " << mChannel->getName();
+                    break;
+                }
+                default: {
+                    LOG(FATAL) << "Unexpected error: " << result.error().message();
+                    break;
+                }
+            }
+        }
+    }
+}
+
+void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
+    switch (msg.header.type) {
+        case InputMessage::Type::KEY: {
+            std::unique_ptr<KeyEvent> keyEvent = createKeyEvent(msg);
+            mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::MOTION: {
+            std::unique_ptr<MotionEvent> motionEvent = createMotionEvent(msg);
+            mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::FINISHED:
+        case InputMessage::Type::TIMELINE: {
+            LOG(FATAL) << "Consumed a " << ftl::enum_string(msg.header.type)
+                       << " message, which should never be seen by InputConsumer on "
+                       << mChannel->getName();
+            break;
+        }
+
+        case InputMessage::Type::FOCUS: {
+            std::unique_ptr<FocusEvent> focusEvent = createFocusEvent(msg);
+            mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::CAPTURE: {
+            std::unique_ptr<CaptureEvent> captureEvent = createCaptureEvent(msg);
+            mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::DRAG: {
+            std::unique_ptr<DragEvent> dragEvent = createDragEvent(msg);
+            mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
+            break;
+        }
+
+        case InputMessage::Type::TOUCH_MODE: {
+            std::unique_ptr<TouchModeEvent> touchModeEvent = createTouchModeEvent(msg);
+            mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
+            break;
+        }
+    }
+}
+
+std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>>
+InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrameTime,
+                                                    std::queue<InputMessage>& messages) {
+    std::unique_ptr<MotionEvent> motionEvent;
+    std::optional<uint32_t> firstSeqForBatch;
+    const nanoseconds resampleLatency =
+            (mResampler != nullptr) ? mResampler->getResampleLatency() : nanoseconds{0};
+    const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency;
+
+    while (!messages.empty() &&
+           (messages.front().body.motion.eventTime <= adjustedFrameTime.count())) {
+        if (motionEvent == nullptr) {
+            motionEvent = createMotionEvent(messages.front());
+            firstSeqForBatch = messages.front().header.seq;
+            const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
+            LOG_IF(FATAL, !inserted)
+                    << "The sequence " << messages.front().header.seq << " was already present!";
+        } else {
+            addSample(*motionEvent, messages.front());
+            mBatchedSequenceNumbers[*firstSeqForBatch].push_back(messages.front().header.seq);
+        }
+        messages.pop();
+    }
+    // Check if resampling should be performed.
+    if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) {
+        InputMessage* futureSample = nullptr;
+        if (!messages.empty()) {
+            futureSample = &messages.front();
+        }
+        mResampler->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
+                                        futureSample);
+    }
+    return std::make_pair(std::move(motionEvent), firstSeqForBatch);
+}
+
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+        std::optional<DeviceId> deviceId, std::optional<nsecs_t> requestedFrameTime) {
+    ensureCalledOnLooperThread(__func__);
+    // When batching is not enabled, we want to consume all events. That's equivalent to having an
+    // infinite requestedFrameTime.
+    requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+    bool producedEvents = false;
+
+    for (auto deviceIdIter = (deviceId.has_value()) ? (mBatches.find(*deviceId))
+                                                    : (mBatches.begin());
+         deviceIdIter != mBatches.cend(); ++deviceIdIter) {
+        std::queue<InputMessage>& messages = deviceIdIter->second;
+        auto [motion, firstSeqForBatch] = createBatchedMotionEvent(*requestedFrameTime, messages);
+        if (motion != nullptr) {
+            LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
+            mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
+            producedEvents = true;
+        } else {
+            // This is OK, it just means that the requestedFrameTime is too old (all events that we
+            // have pending are in the future of the requestedFrameTime). Maybe print a warning? If
+            // there are multiple devices active though, this might be normal and can just be
+            // ignored, unless none of them resulted in any consumption (in that case, this function
+            // would already return "false" so we could just leave it up to the caller).
+        }
+
+        if (deviceId.has_value()) {
+            // We already consumed events for this device. Break here to prevent iterating over the
+            // other devices.
+            break;
+        }
+    }
+    std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
+    return producedEvents;
+}
+
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+        std::optional<nsecs_t> requestedFrameTime) {
+    return consumeBatchedInputEvents(/*deviceId=*/std::nullopt, requestedFrameTime);
+}
+
+void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
+    sp<Looper> callingThreadLooper = Looper::getForThread();
+    if (callingThreadLooper != mLooper) {
+        LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
+    }
+}
+
+std::string InputConsumerNoResampling::dump() const {
+    ensureCalledOnLooperThread(__func__);
+    std::string out;
+    if (mOutboundQueue.empty()) {
+        out += "mOutboundQueue: <empty>\n";
+    } else {
+        out += "mOutboundQueue:\n";
+        // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+        // doesn't provide a good way to iterate over the entire container.
+        std::queue<InputMessage> tmpQueue = mOutboundQueue;
+        while (!tmpQueue.empty()) {
+            out += std::string("  ") + outboundMessageToString(tmpQueue.front()) + "\n";
+            tmpQueue.pop();
+        }
+    }
+
+    if (mBatches.empty()) {
+        out += "mBatches: <empty>\n";
+    } else {
+        out += "mBatches:\n";
+        for (const auto& [deviceId, messages] : mBatches) {
+            out += "  Device id ";
+            out += std::to_string(deviceId);
+            out += ":\n";
+            // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
+            // doesn't provide a good way to iterate over the entire container.
+            std::queue<InputMessage> tmpQueue = messages;
+            while (!tmpQueue.empty()) {
+                LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
+                std::unique_ptr<MotionEvent> motion = createMotionEvent(tmpQueue.front());
+                out += std::string("    ") + streamableToString(*motion) + "\n";
+                tmpQueue.pop();
+            }
+        }
+    }
+
+    return out;
+}
+
+} // namespace android
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index c348833..c903031 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -20,10 +20,10 @@
 #include <unistd.h>
 #include <ctype.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
-#include <gui/constants.h>
 #include <input/InputDevice.h>
 #include <input/InputEventLabels.h>
 
@@ -32,6 +32,9 @@
 
 namespace android {
 
+// Set to true to log detailed debugging messages about IDC file probing.
+static constexpr bool DEBUG_PROBE = false;
+
 static const char* CONFIGURATION_FILE_DIR[] = {
         "idc/",
         "keylayout/",
@@ -115,15 +118,18 @@
     for (const auto& prefix : pathPrefixes) {
         path = prefix;
         appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-        ALOGD("Probing for system provided input device configuration file: path='%s'",
-              path.c_str());
-#endif
         if (!access(path.c_str(), R_OK)) {
-#if DEBUG_PROBE
-            ALOGD("Found");
-#endif
+            LOG_IF(INFO, DEBUG_PROBE)
+                    << "Found system-provided input device configuration file at " << path;
             return path;
+        } else if (errno != ENOENT) {
+            LOG(WARNING) << "Couldn't find a system-provided input device configuration file at "
+                         << path << " due to error " << errno << " (" << strerror(errno)
+                         << "); there may be an IDC file there that cannot be loaded.";
+        } else {
+            LOG_IF(ERROR, DEBUG_PROBE)
+                    << "Didn't find system-provided input device configuration file at " << path
+                    << ": " << strerror(errno);
         }
     }
 
@@ -136,21 +142,22 @@
     }
     path += "/system/devices/";
     appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-    ALOGD("Probing for system user input device configuration file: path='%s'", path.c_str());
-#endif
     if (!access(path.c_str(), R_OK)) {
-#if DEBUG_PROBE
-        ALOGD("Found");
-#endif
+        LOG_IF(INFO, DEBUG_PROBE) << "Found system user input device configuration file at "
+                                  << path;
         return path;
+    } else if (errno != ENOENT) {
+        LOG(WARNING) << "Couldn't find a system user input device configuration file at " << path
+                     << " due to error " << errno << " (" << strerror(errno)
+                     << "); there may be an IDC file there that cannot be loaded.";
+    } else {
+        LOG_IF(ERROR, DEBUG_PROBE) << "Didn't find system user input device configuration file at "
+                                   << path << ": " << strerror(errno);
     }
 
     // Not found.
-#if DEBUG_PROBE
-    ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
-            name.c_str(), type);
-#endif
+    LOG_IF(INFO, DEBUG_PROBE) << "Probe failed to find input device configuration file with name '"
+                              << name << "' and type " << ftl::enum_string(type);
     return "";
 }
 
@@ -170,7 +177,7 @@
 // --- InputDeviceInfo ---
 
 InputDeviceInfo::InputDeviceInfo() {
-    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
+    initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ui::LogicalDisplayId::INVALID);
 }
 
 InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -187,6 +194,7 @@
         mKeyCharacterMap(other.mKeyCharacterMap),
         mUsiVersion(other.mUsiVersion),
         mAssociatedDisplayId(other.mAssociatedDisplayId),
+        mEnabled(other.mEnabled),
         mHasVibrator(other.mHasVibrator),
         mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -201,8 +209,9 @@
 
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
-                                 bool isExternal, bool hasMic, int32_t associatedDisplayId,
-                                 InputDeviceViewBehavior viewBehavior) {
+                                 bool isExternal, bool hasMic,
+                                 ui::LogicalDisplayId associatedDisplayId,
+                                 InputDeviceViewBehavior viewBehavior, bool enabled) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -213,6 +222,7 @@
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
     mAssociatedDisplayId = associatedDisplayId;
+    mEnabled = enabled;
     mHasVibrator = false;
     mHasBattery = false;
     mHasButtonUnderPad = false;
@@ -271,10 +281,7 @@
 }
 
 void InputDeviceInfo::setKeyboardType(int32_t keyboardType) {
-    static_assert(AINPUT_KEYBOARD_TYPE_NONE < AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
-    static_assert(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC < AINPUT_KEYBOARD_TYPE_ALPHABETIC);
-    // There can be multiple subdevices with different keyboard types, set it to the highest type
-    mKeyboardType = std::max(mKeyboardType, keyboardType);
+    mKeyboardType = keyboardType;
 }
 
 void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index e49f4eb..77dcaa9 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -26,10 +26,13 @@
 
 #include <com_android_input_flags.h>
 #include <input/InputTransport.h>
+#include <input/PrintTools.h>
 #include <input/TraceTools.h>
 
 namespace input_flags = com::android::input::flags;
 
+namespace android {
+
 namespace {
 
 /**
@@ -48,14 +51,6 @@
 const bool DEBUG_CHANNEL_LIFECYCLE =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO);
 
-/**
- * Log debug messages relating to the consumer end of the transport channel.
- * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
- */
-
-const bool DEBUG_TRANSPORT_CONSUMER =
-        __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
-
 const bool IS_DEBUGGABLE_BUILD =
 #if defined(__ANDROID__)
         android::base::GetBoolProperty("ro.debuggable", false);
@@ -78,23 +73,6 @@
     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
 }
 
-/**
- * Log debug messages about touch event resampling.
- *
- * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
- * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
- * on debuggable builds (e.g. userdebug).
- */
-bool debugResampling() {
-    if (!IS_DEBUGGABLE_BUILD) {
-        static const bool DEBUG_TRANSPORT_RESAMPLING =
-                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
-                                          ANDROID_LOG_INFO);
-        return DEBUG_TRANSPORT_RESAMPLING;
-    }
-    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
-}
-
 android::base::unique_fd dupChannelFd(int fd) {
     android::base::unique_fd newFd(::dup(fd));
     if (!newFd.ok()) {
@@ -110,78 +88,25 @@
     return newFd;
 }
 
-} // namespace
-
-using android::base::Result;
-using android::base::StringPrintf;
-
-namespace android {
-
 // Socket buffer size.  The default is typically about 128KB, which is much larger than
 // we really need.  So we make it smaller.  It just needs to be big enough to hold
 // a few dozen large multi-finger motion events in the case where an application gets
 // behind processing touches.
-static const size_t SOCKET_BUFFER_SIZE = 32 * 1024;
-
-// Nanoseconds per milliseconds.
-static const nsecs_t NANOS_PER_MS = 1000000;
-
-// Latency added during resampling.  A few milliseconds doesn't hurt much but
-// reduces the impact of mispredicted touch positions.
-const std::chrono::duration RESAMPLE_LATENCY = 5ms;
-
-// Minimum time difference between consecutive samples before attempting to resample.
-static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
-
-// Maximum time difference between consecutive samples before attempting to resample
-// by extrapolation.
-static const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
-
-// Maximum time to predict forward from the last known state, to avoid predicting too
-// far into the future.  This time is further bounded by 50% of the last time delta.
-static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
-
-/**
- * System property for enabling / disabling touch resampling.
- * Resampling extrapolates / interpolates the reported touch event coordinates to better
- * align them to the VSYNC signal, thus resulting in smoother scrolling performance.
- * Resampling is not needed (and should be disabled) on hardware that already
- * has touch events triggered by VSYNC.
- * Set to "1" to enable resampling (default).
- * Set to "0" to disable resampling.
- * Resampling is enabled by default.
- */
-static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+constexpr size_t SOCKET_BUFFER_SIZE = 32 * 1024;
 
 /**
  * Crash if the events that are getting sent to the InputPublisher are inconsistent.
  * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
  */
-static bool verifyEvents() {
+bool verifyEvents() {
     return input_flags::enable_outbound_event_verification() ||
             __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
 }
 
-template<typename T>
-inline static T min(const T& a, const T& b) {
-    return a < b ? a : b;
-}
+} // namespace
 
-inline static float lerp(float a, float b, float alpha) {
-    return a + alpha * (b - a);
-}
-
-inline static bool isPointerEvent(int32_t source) {
-    return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
-}
-
-inline static const char* toString(bool value) {
-    return value ? "true" : "false";
-}
-
-static bool shouldResampleTool(ToolType toolType) {
-    return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
-}
+using android::base::Result;
+using android::base::StringPrintf;
 
 // --- InputMessage ---
 
@@ -450,21 +375,19 @@
 
     sp<IBinder> token = sp<BBinder>::make();
 
-    std::string serverChannelName = name + " (server)";
     android::base::unique_fd serverFd(sockets[0]);
-    outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token);
+    outServerChannel = InputChannel::create(name, std::move(serverFd), token);
 
-    std::string clientChannelName = name + " (client)";
     android::base::unique_fd clientFd(sockets[1]);
-    outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token);
+    outClientChannel = InputChannel::create(name, std::move(clientFd), token);
     return OK;
 }
 
 status_t InputChannel::sendMessage(const InputMessage* msg) {
     ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
-                                ")",
-                                name.c_str(), msg->header.seq, msg->header.type));
+                   StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                                name.c_str(), msg->header.seq,
+                                ftl::enum_string(msg->header.type).c_str()));
     const size_t msgLength = msg->size();
     InputMessage cleanMsg;
     msg->getSanitizedCopy(&cleanMsg);
@@ -499,10 +422,11 @@
     return OK;
 }
 
-status_t InputChannel::receiveMessage(InputMessage* msg) {
+android::base::Result<InputMessage> InputChannel::receiveMessage() {
     ssize_t nRead;
+    InputMessage msg;
     do {
-        nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+        nRead = ::recv(getFd(), &msg, sizeof(InputMessage), MSG_DONTWAIT);
     } while (nRead == -1 && errno == EINTR);
 
     if (nRead < 0) {
@@ -510,35 +434,36 @@
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
                  name.c_str(), errno);
         if (error == EAGAIN || error == EWOULDBLOCK) {
-            return WOULD_BLOCK;
+            return android::base::Error(WOULD_BLOCK);
         }
         if (error == EPIPE || error == ENOTCONN || error == ECONNREFUSED) {
-            return DEAD_OBJECT;
+            return android::base::Error(DEAD_OBJECT);
         }
-        return -error;
+        return android::base::Error(-error);
     }
 
     if (nRead == 0) { // check for EOF
         ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
                  "channel '%s' ~ receive message failed because peer was closed", name.c_str());
-        return DEAD_OBJECT;
+        return android::base::Error(DEAD_OBJECT);
     }
 
-    if (!msg->isValid(nRead)) {
+    if (!msg.isValid(nRead)) {
         ALOGE("channel '%s' ~ received invalid message of size %zd", name.c_str(), nRead);
-        return BAD_VALUE;
+        return android::base::Error(BAD_VALUE);
     }
 
     ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", name.c_str(),
-             ftl::enum_string(msg->header.type).c_str());
+             ftl::enum_string(msg.header.type).c_str());
     if (ATRACE_ENABLED()) {
         // Add an additional trace point to include data about the received message.
-        std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
-                                           ", type=0x%" PRIx32 ")",
-                                           name.c_str(), msg->header.seq, msg->header.type);
+        std::string message =
+                StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=%s)",
+                             name.c_str(), msg.header.seq,
+                             ftl::enum_string(msg.header.type).c_str());
         ATRACE_NAME(message.c_str());
     }
-    return OK;
+    return msg;
 }
 
 bool InputChannel::probablyHasInput() const {
@@ -604,7 +529,7 @@
 }
 
 status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t deviceId,
-                                         int32_t source, int32_t displayId,
+                                         int32_t source, ui::LogicalDisplayId displayId,
                                          std::array<uint8_t, 32> hmac, int32_t action,
                                          int32_t flags, int32_t keyCode, int32_t scanCode,
                                          int32_t metaState, int32_t repeatCount, nsecs_t downTime,
@@ -632,7 +557,7 @@
     msg.body.key.eventId = eventId;
     msg.body.key.deviceId = deviceId;
     msg.body.key.source = source;
-    msg.body.key.displayId = displayId;
+    msg.body.key.displayId = displayId.val();
     msg.body.key.hmac = std::move(hmac);
     msg.body.key.action = action;
     msg.body.key.flags = flags;
@@ -646,11 +571,11 @@
 }
 
 status_t InputPublisher::publishMotionEvent(
-        uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source, int32_t displayId,
-        std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
-        int32_t edgeFlags, int32_t metaState, int32_t buttonState,
-        MotionClassification classification, const ui::Transform& transform, float xPrecision,
-        float yPrecision, float xCursorPosition, float yCursorPosition,
+        uint32_t seq, int32_t eventId, int32_t deviceId, int32_t source,
+        ui::LogicalDisplayId displayId, std::array<uint8_t, 32> hmac, int32_t action,
+        int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+        int32_t buttonState, MotionClassification classification, const ui::Transform& transform,
+        float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
         const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
@@ -663,20 +588,21 @@
                 mInputVerifier.processMovement(deviceId, source, action, pointerCount,
                                                pointerProperties, pointerCoords, flags);
         if (!result.ok()) {
-            LOG(FATAL) << "Bad stream: " << result.error();
+            LOG(ERROR) << "Bad stream: " << result.error();
+            return BAD_VALUE;
         }
     }
     if (debugTransportPublisher()) {
         std::string transformString;
         transform.dump(transformString, "transform", "        ");
         ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
-              "displayId=%" PRId32 ", "
+              "displayId=%s, "
               "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
               "metaState=0x%x, buttonState=0x%x, classification=%s,"
               "xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
               "pointerCount=%" PRIu32 "\n%s",
               mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
-              inputEventSourceToString(source).c_str(), displayId,
+              inputEventSourceToString(source).c_str(), displayId.toString().c_str(),
               MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
               metaState, buttonState, motionClassificationToString(classification), xPrecision,
               yPrecision, downTime, eventTime, pointerCount, transformString.c_str());
@@ -699,7 +625,7 @@
     msg.body.motion.eventId = eventId;
     msg.body.motion.deviceId = deviceId;
     msg.body.motion.source = source;
-    msg.body.motion.displayId = displayId;
+    msg.body.motion.displayId = displayId.val();
     msg.body.motion.hmac = std::move(hmac);
     msg.body.motion.action = action;
     msg.body.motion.actionButton = actionButton;
@@ -803,15 +729,16 @@
 }
 
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
-    InputMessage msg;
-    status_t result = mChannel->receiveMessage(&msg);
-    if (result) {
-        if (debugTransportPublisher() && result != WOULD_BLOCK) {
+    android::base::Result<InputMessage> result = mChannel->receiveMessage();
+    if (!result.ok()) {
+        if (debugTransportPublisher() && result.error().code() != WOULD_BLOCK) {
             LOG(INFO) << "channel '" << mChannel->getName() << "' publisher ~ " << __func__ << ": "
-                      << strerror(result);
+                      << result.error().message();
         }
-        return android::base::Error(result);
+        return result.error();
     }
+
+    const InputMessage& msg = *result;
     if (msg.header.type == InputMessage::Type::FINISHED) {
         ALOGD_IF(debugTransportPublisher(),
                  "channel '%s' publisher ~ %s: finished: seq=%u, handled=%s",
@@ -838,819 +765,4 @@
     return android::base::Error(UNKNOWN_ERROR);
 }
 
-// --- InputConsumer ---
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
-      : InputConsumer(channel, isTouchResamplingEnabled()) {}
-
-InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
-                             bool enableTouchResampling)
-      : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
-
-InputConsumer::~InputConsumer() {
-}
-
-bool InputConsumer::isTouchResamplingEnabled() {
-    return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
-}
-
-status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
-                                nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
-             mChannel->getName().c_str(), toString(consumeBatches), frameTime);
-
-    *outSeq = 0;
-    *outEvent = nullptr;
-
-    // Fetch the next input message.
-    // Loop until an event can be returned or no additional events are received.
-    while (!*outEvent) {
-        if (mMsgDeferred) {
-            // mMsg contains a valid input message from the previous call to consume
-            // that has not yet been processed.
-            mMsgDeferred = false;
-        } else {
-            // Receive a fresh message.
-            status_t result = mChannel->receiveMessage(&mMsg);
-            if (result == OK) {
-                const auto [_, inserted] =
-                        mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
-                LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
-                                    mMsg.header.seq);
-
-                // Trace the event processing timeline - event was just read from the socket
-                ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/mMsg.header.seq);
-            }
-            if (result) {
-                // Consume the next batched event unless batches are being held for later.
-                if (consumeBatches || result != WOULD_BLOCK) {
-                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
-                    if (*outEvent) {
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ consumed batch event, seq=%u",
-                                 mChannel->getName().c_str(), *outSeq);
-                        break;
-                    }
-                }
-                return result;
-            }
-        }
-
-        switch (mMsg.header.type) {
-            case InputMessage::Type::KEY: {
-                KeyEvent* keyEvent = factory->createKeyEvent();
-                if (!keyEvent) return NO_MEMORY;
-
-                initializeKeyEvent(keyEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = keyEvent;
-                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                         "channel '%s' consumer ~ consumed key event, seq=%u",
-                         mChannel->getName().c_str(), *outSeq);
-                break;
-            }
-
-            case InputMessage::Type::MOTION: {
-                ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
-                if (batchIndex >= 0) {
-                    Batch& batch = mBatches[batchIndex];
-                    if (canAddSample(batch, &mMsg)) {
-                        batch.samples.push_back(mMsg);
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ appended to batch event",
-                                 mChannel->getName().c_str());
-                        break;
-                    } else if (isPointerEvent(mMsg.body.motion.source) &&
-                               mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
-                        // No need to process events that we are going to cancel anyways
-                        const size_t count = batch.samples.size();
-                        for (size_t i = 0; i < count; i++) {
-                            const InputMessage& msg = batch.samples[i];
-                            sendFinishedSignal(msg.header.seq, false);
-                        }
-                        batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
-                        mBatches.erase(mBatches.begin() + batchIndex);
-                    } else {
-                        // We cannot append to the batch in progress, so we need to consume
-                        // the previous batch right now and defer the new message until later.
-                        mMsgDeferred = true;
-                        status_t result = consumeSamples(factory, batch, batch.samples.size(),
-                                                         outSeq, outEvent);
-                        mBatches.erase(mBatches.begin() + batchIndex);
-                        if (result) {
-                            return result;
-                        }
-                        ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                                 "channel '%s' consumer ~ consumed batch event and "
-                                 "deferred current event, seq=%u",
-                                 mChannel->getName().c_str(), *outSeq);
-                        break;
-                    }
-                }
-
-                // Start a new batch if needed.
-                if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
-                    mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
-                    Batch batch;
-                    batch.samples.push_back(mMsg);
-                    mBatches.push_back(batch);
-                    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                             "channel '%s' consumer ~ started batch event",
-                             mChannel->getName().c_str());
-                    break;
-                }
-
-                MotionEvent* motionEvent = factory->createMotionEvent();
-                if (!motionEvent) return NO_MEMORY;
-
-                updateTouchState(mMsg);
-                initializeMotionEvent(motionEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = motionEvent;
-
-                ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-                         "channel '%s' consumer ~ consumed motion event, seq=%u",
-                         mChannel->getName().c_str(), *outSeq);
-                break;
-            }
-
-            case InputMessage::Type::FINISHED:
-            case InputMessage::Type::TIMELINE: {
-                LOG_ALWAYS_FATAL("Consumed a %s message, which should never be seen by "
-                                 "InputConsumer!",
-                                 ftl::enum_string(mMsg.header.type).c_str());
-                break;
-            }
-
-            case InputMessage::Type::FOCUS: {
-                FocusEvent* focusEvent = factory->createFocusEvent();
-                if (!focusEvent) return NO_MEMORY;
-
-                initializeFocusEvent(focusEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = focusEvent;
-                break;
-            }
-
-            case InputMessage::Type::CAPTURE: {
-                CaptureEvent* captureEvent = factory->createCaptureEvent();
-                if (!captureEvent) return NO_MEMORY;
-
-                initializeCaptureEvent(captureEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = captureEvent;
-                break;
-            }
-
-            case InputMessage::Type::DRAG: {
-                DragEvent* dragEvent = factory->createDragEvent();
-                if (!dragEvent) return NO_MEMORY;
-
-                initializeDragEvent(dragEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = dragEvent;
-                break;
-            }
-
-            case InputMessage::Type::TOUCH_MODE: {
-                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
-                if (!touchModeEvent) return NO_MEMORY;
-
-                initializeTouchModeEvent(touchModeEvent, &mMsg);
-                *outSeq = mMsg.header.seq;
-                *outEvent = touchModeEvent;
-                break;
-            }
-        }
-    }
-    return OK;
-}
-
-status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
-        nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
-    status_t result;
-    for (size_t i = mBatches.size(); i > 0; ) {
-        i--;
-        Batch& batch = mBatches[i];
-        if (frameTime < 0) {
-            result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
-            mBatches.erase(mBatches.begin() + i);
-            return result;
-        }
-
-        nsecs_t sampleTime = frameTime;
-        if (mResampleTouch) {
-            sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
-        }
-        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
-        if (split < 0) {
-            continue;
-        }
-
-        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
-        const InputMessage* next;
-        if (batch.samples.empty()) {
-            mBatches.erase(mBatches.begin() + i);
-            next = nullptr;
-        } else {
-            next = &batch.samples[0];
-        }
-        if (!result && mResampleTouch) {
-            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
-        }
-        return result;
-    }
-
-    return WOULD_BLOCK;
-}
-
-status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory,
-        Batch& batch, size_t count, uint32_t* outSeq, InputEvent** outEvent) {
-    MotionEvent* motionEvent = factory->createMotionEvent();
-    if (! motionEvent) return NO_MEMORY;
-
-    uint32_t chain = 0;
-    for (size_t i = 0; i < count; i++) {
-        InputMessage& msg = batch.samples[i];
-        updateTouchState(msg);
-        if (i) {
-            SeqChain seqChain;
-            seqChain.seq = msg.header.seq;
-            seqChain.chain = chain;
-            mSeqChains.push_back(seqChain);
-            addSample(motionEvent, &msg);
-        } else {
-            initializeMotionEvent(motionEvent, &msg);
-        }
-        chain = msg.header.seq;
-    }
-    batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
-
-    *outSeq = chain;
-    *outEvent = motionEvent;
-    return OK;
-}
-
-void InputConsumer::updateTouchState(InputMessage& msg) {
-    if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
-        return;
-    }
-
-    int32_t deviceId = msg.body.motion.deviceId;
-    int32_t source = msg.body.motion.source;
-
-    // Update the touch state history to incorporate the new input message.
-    // If the message is in the past relative to the most recently produced resampled
-    // touch, then use the resampled time and coordinates instead.
-    switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
-    case AMOTION_EVENT_ACTION_DOWN: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index < 0) {
-            mTouchStates.push_back({});
-            index = mTouchStates.size() - 1;
-        }
-        TouchState& touchState = mTouchStates[index];
-        touchState.initialize(deviceId, source);
-        touchState.addHistory(msg);
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_MOVE: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            touchState.addHistory(msg);
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_POINTER_DOWN: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_POINTER_UP: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-            touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_SCROLL: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-        }
-        break;
-    }
-
-    case AMOTION_EVENT_ACTION_UP:
-    case AMOTION_EVENT_ACTION_CANCEL: {
-        ssize_t index = findTouchState(deviceId, source);
-        if (index >= 0) {
-            TouchState& touchState = mTouchStates[index];
-            rewriteMessage(touchState, msg);
-            mTouchStates.erase(mTouchStates.begin() + index);
-        }
-        break;
-    }
-    }
-}
-
-/**
- * Replace the coordinates in msg with the coordinates in lastResample, if necessary.
- *
- * If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
- * is in the past relative to msg and the past two events do not contain identical coordinates),
- * then invalidate the lastResample data for that pointer.
- * If the two past events have identical coordinates, then lastResample data for that pointer will
- * remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
- * resampled to the new value x1, then x1 will always be used to replace x0 until some new value
- * not equal to x0 is received.
- */
-void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
-    nsecs_t eventTime = msg.body.motion.eventTime;
-    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-        uint32_t id = msg.body.motion.pointers[i].properties.id;
-        if (state.lastResample.idBits.hasBit(id)) {
-            if (eventTime < state.lastResample.eventTime ||
-                    state.recentCoordinatesAreIdentical(id)) {
-                PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
-                const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
-                ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
-                         resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
-                         msgCoords.getY());
-                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
-                msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
-                msgCoords.isResampled = true;
-            } else {
-                state.lastResample.idBits.clearBit(id);
-            }
-        }
-    }
-}
-
-void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
-    const InputMessage* next) {
-    if (!mResampleTouch
-            || !(isPointerEvent(event->getSource()))
-            || event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
-        return;
-    }
-
-    ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
-    if (index < 0) {
-        ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
-        return;
-    }
-
-    TouchState& touchState = mTouchStates[index];
-    if (touchState.historySize < 1) {
-        ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
-        return;
-    }
-
-    // Ensure that the current sample has all of the pointers that need to be reported.
-    const History* current = touchState.getHistory(0);
-    size_t pointerCount = event->getPointerCount();
-    for (size_t i = 0; i < pointerCount; i++) {
-        uint32_t id = event->getPointerId(i);
-        if (!current->idBits.hasBit(id)) {
-            ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
-            return;
-        }
-    }
-
-    // Find the data to use for resampling.
-    const History* other;
-    History future;
-    float alpha;
-    if (next) {
-        // Interpolate between current sample and future sample.
-        // So current->eventTime <= sampleTime <= future.eventTime.
-        future.initializeFrom(*next);
-        other = &future;
-        nsecs_t delta = future.eventTime - current->eventTime;
-        if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
-                     delta);
-            return;
-        }
-        alpha = float(sampleTime - current->eventTime) / delta;
-    } else if (touchState.historySize >= 2) {
-        // Extrapolate future sample using current sample and past sample.
-        // So other->eventTime <= current->eventTime <= sampleTime.
-        other = touchState.getHistory(1);
-        nsecs_t delta = current->eventTime - other->eventTime;
-        if (delta < RESAMPLE_MIN_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
-                     delta);
-            return;
-        } else if (delta > RESAMPLE_MAX_DELTA) {
-            ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
-                     delta);
-            return;
-        }
-        nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
-        if (sampleTime > maxPredict) {
-            ALOGD_IF(debugResampling(),
-                     "Sample time is too far in the future, adjusting prediction "
-                     "from %" PRId64 " to %" PRId64 " ns.",
-                     sampleTime - current->eventTime, maxPredict - current->eventTime);
-            sampleTime = maxPredict;
-        }
-        alpha = float(current->eventTime - sampleTime) / delta;
-    } else {
-        ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
-        return;
-    }
-
-    if (current->eventTime == sampleTime) {
-        // Prevents having 2 events with identical times and coordinates.
-        return;
-    }
-
-    // Resample touch coordinates.
-    History oldLastResample;
-    oldLastResample.initializeFrom(touchState.lastResample);
-    touchState.lastResample.eventTime = sampleTime;
-    touchState.lastResample.idBits.clear();
-    for (size_t i = 0; i < pointerCount; i++) {
-        uint32_t id = event->getPointerId(i);
-        touchState.lastResample.idToIndex[id] = i;
-        touchState.lastResample.idBits.markBit(id);
-        if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
-            // We maintain the previously resampled value for this pointer (stored in
-            // oldLastResample) when the coordinates for this pointer haven't changed since then.
-            // This way we don't introduce artificial jitter when pointers haven't actually moved.
-            // The isResampled flag isn't cleared as the values don't reflect what the device is
-            // actually reporting.
-
-            // We know here that the coordinates for the pointer haven't changed because we
-            // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
-            // lastResample in place becasue the mapping from pointer ID to index may have changed.
-            touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
-            continue;
-        }
-
-        PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
-        const PointerCoords& currentCoords = current->getPointerById(id);
-        resampledCoords = currentCoords;
-        resampledCoords.isResampled = true;
-        if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
-            const PointerCoords& otherCoords = other->getPointerById(id);
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
-                                         lerp(currentCoords.getX(), otherCoords.getX(), alpha));
-            resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
-                                         lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-            ALOGD_IF(debugResampling(),
-                     "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
-                     "other (%0.3f, %0.3f), alpha %0.3f",
-                     id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
-        } else {
-            ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
-                     resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
-                     currentCoords.getY());
-        }
-    }
-
-    event->addSample(sampleTime, touchState.lastResample.pointers);
-}
-
-status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
-             mChannel->getName().c_str(), seq, toString(handled));
-
-    if (!seq) {
-        ALOGE("Attempted to send a finished signal with sequence number 0.");
-        return BAD_VALUE;
-    }
-
-    // Send finished signals for the batch sequence chain first.
-    size_t seqChainCount = mSeqChains.size();
-    if (seqChainCount) {
-        uint32_t currentSeq = seq;
-        uint32_t chainSeqs[seqChainCount];
-        size_t chainIndex = 0;
-        for (size_t i = seqChainCount; i > 0; ) {
-             i--;
-             const SeqChain& seqChain = mSeqChains[i];
-             if (seqChain.seq == currentSeq) {
-                 currentSeq = seqChain.chain;
-                 chainSeqs[chainIndex++] = currentSeq;
-                 mSeqChains.erase(mSeqChains.begin() + i);
-             }
-        }
-        status_t status = OK;
-        while (!status && chainIndex > 0) {
-            chainIndex--;
-            status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
-        }
-        if (status) {
-            // An error occurred so at least one signal was not sent, reconstruct the chain.
-            for (;;) {
-                SeqChain seqChain;
-                seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
-                seqChain.chain = chainSeqs[chainIndex];
-                mSeqChains.push_back(seqChain);
-                if (!chainIndex) break;
-                chainIndex--;
-            }
-            return status;
-        }
-    }
-
-    // Send finished signal for the last message in the batch.
-    return sendUnchainedFinishedSignal(seq, handled);
-}
-
-status_t InputConsumer::sendTimeline(int32_t inputEventId,
-                                     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
-    ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
-             "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
-             ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
-             mChannel->getName().c_str(), inputEventId,
-             graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
-             graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
-
-    InputMessage msg;
-    msg.header.type = InputMessage::Type::TIMELINE;
-    msg.header.seq = 0;
-    msg.body.timeline.eventId = inputEventId;
-    msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
-    return mChannel->sendMessage(&msg);
-}
-
-nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
-    auto it = mConsumeTimes.find(seq);
-    // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
-    // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
-    LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
-                        seq);
-    return it->second;
-}
-
-void InputConsumer::popConsumeTime(uint32_t seq) {
-    mConsumeTimes.erase(seq);
-}
-
-status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
-    InputMessage msg;
-    msg.header.type = InputMessage::Type::FINISHED;
-    msg.header.seq = seq;
-    msg.body.finished.handled = handled;
-    msg.body.finished.consumeTime = getConsumeTime(seq);
-    status_t result = mChannel->sendMessage(&msg);
-    if (result == OK) {
-        // Remove the consume time if the socket write succeeded. We will not need to ack this
-        // message anymore. If the socket write did not succeed, we will try again and will still
-        // need consume time.
-        popConsumeTime(seq);
-
-        // Trace the event processing timeline - event was just finished
-        ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/seq);
-    }
-    return result;
-}
-
-bool InputConsumer::hasPendingBatch() const {
-    return !mBatches.empty();
-}
-
-int32_t InputConsumer::getPendingBatchSource() const {
-    if (mBatches.empty()) {
-        return AINPUT_SOURCE_CLASS_NONE;
-    }
-
-    const Batch& batch = mBatches[0];
-    const InputMessage& head = batch.samples[0];
-    return head.body.motion.source;
-}
-
-bool InputConsumer::probablyHasInput() const {
-    return hasPendingBatch() || mChannel->probablyHasInput();
-}
-
-ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
-    for (size_t i = 0; i < mBatches.size(); i++) {
-        const Batch& batch = mBatches[i];
-        const InputMessage& head = batch.samples[0];
-        if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
-    for (size_t i = 0; i < mTouchStates.size(); i++) {
-        const TouchState& touchState = mTouchStates[i];
-        if (touchState.deviceId == deviceId && touchState.source == source) {
-            return i;
-        }
-    }
-    return -1;
-}
-
-void InputConsumer::initializeKeyEvent(KeyEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.key.eventId, msg->body.key.deviceId, msg->body.key.source,
-                      msg->body.key.displayId, msg->body.key.hmac, msg->body.key.action,
-                      msg->body.key.flags, msg->body.key.keyCode, msg->body.key.scanCode,
-                      msg->body.key.metaState, msg->body.key.repeatCount, msg->body.key.downTime,
-                      msg->body.key.eventTime);
-}
-
-void InputConsumer::initializeFocusEvent(FocusEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.focus.eventId, msg->body.focus.hasFocus);
-}
-
-void InputConsumer::initializeCaptureEvent(CaptureEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.capture.eventId, msg->body.capture.pointerCaptureEnabled);
-}
-
-void InputConsumer::initializeDragEvent(DragEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.drag.eventId, msg->body.drag.x, msg->body.drag.y,
-                      msg->body.drag.isExiting);
-}
-
-void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage* msg) {
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    PointerProperties pointerProperties[pointerCount];
-    PointerCoords pointerCoords[pointerCount];
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerProperties[i] = msg->body.motion.pointers[i].properties;
-        pointerCoords[i] = msg->body.motion.pointers[i].coords;
-    }
-
-    ui::Transform transform;
-    transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx,
-                   msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1});
-    ui::Transform displayTransform;
-    displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw,
-                          msg->body.motion.txRaw, msg->body.motion.dtdyRaw,
-                          msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1});
-    event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
-                      msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
-                      msg->body.motion.actionButton, msg->body.motion.flags,
-                      msg->body.motion.edgeFlags, msg->body.motion.metaState,
-                      msg->body.motion.buttonState, msg->body.motion.classification, transform,
-                      msg->body.motion.xPrecision, msg->body.motion.yPrecision,
-                      msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
-                      displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime,
-                      pointerCount, pointerProperties, pointerCoords);
-}
-
-void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
-    event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode);
-}
-
-void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    PointerCoords pointerCoords[pointerCount];
-    for (uint32_t i = 0; i < pointerCount; i++) {
-        pointerCoords[i] = msg->body.motion.pointers[i].coords;
-    }
-
-    event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
-    event->addSample(msg->body.motion.eventTime, pointerCoords);
-}
-
-bool InputConsumer::canAddSample(const Batch& batch, const InputMessage *msg) {
-    const InputMessage& head = batch.samples[0];
-    uint32_t pointerCount = msg->body.motion.pointerCount;
-    if (head.body.motion.pointerCount != pointerCount
-            || head.body.motion.action != msg->body.motion.action) {
-        return false;
-    }
-    for (size_t i = 0; i < pointerCount; i++) {
-        if (head.body.motion.pointers[i].properties
-                != msg->body.motion.pointers[i].properties) {
-            return false;
-        }
-    }
-    return true;
-}
-
-ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
-    size_t numSamples = batch.samples.size();
-    size_t index = 0;
-    while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
-        index += 1;
-    }
-    return ssize_t(index) - 1;
-}
-
-std::string InputConsumer::dump() const {
-    std::string out;
-    out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
-    out = out + "mChannel = " + mChannel->getName() + "\n";
-    out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
-    if (mMsgDeferred) {
-        out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
-    }
-    out += "Batches:\n";
-    for (const Batch& batch : mBatches) {
-        out += "    Batch:\n";
-        for (const InputMessage& msg : batch.samples) {
-            out += android::base::StringPrintf("        Message %" PRIu32 ": %s ", msg.header.seq,
-                                               ftl::enum_string(msg.header.type).c_str());
-            switch (msg.header.type) {
-                case InputMessage::Type::KEY: {
-                    out += android::base::StringPrintf("action=%s keycode=%" PRId32,
-                                                       KeyEvent::actionToString(
-                                                               msg.body.key.action),
-                                                       msg.body.key.keyCode);
-                    break;
-                }
-                case InputMessage::Type::MOTION: {
-                    out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
-                    for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
-                        const float x = msg.body.motion.pointers[i].coords.getX();
-                        const float y = msg.body.motion.pointers[i].coords.getY();
-                        out += android::base::StringPrintf("\n            Pointer %" PRIu32
-                                                           " : x=%.1f y=%.1f",
-                                                           i, x, y);
-                    }
-                    break;
-                }
-                case InputMessage::Type::FINISHED: {
-                    out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
-                                                       toString(msg.body.finished.handled),
-                                                       msg.body.finished.consumeTime);
-                    break;
-                }
-                case InputMessage::Type::FOCUS: {
-                    out += android::base::StringPrintf("hasFocus=%s",
-                                                       toString(msg.body.focus.hasFocus));
-                    break;
-                }
-                case InputMessage::Type::CAPTURE: {
-                    out += android::base::StringPrintf("hasCapture=%s",
-                                                       toString(msg.body.capture
-                                                                        .pointerCaptureEnabled));
-                    break;
-                }
-                case InputMessage::Type::DRAG: {
-                    out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
-                                                       msg.body.drag.x, msg.body.drag.y,
-                                                       toString(msg.body.drag.isExiting));
-                    break;
-                }
-                case InputMessage::Type::TIMELINE: {
-                    const nsecs_t gpuCompletedTime =
-                            msg.body.timeline
-                                    .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
-                    const nsecs_t presentTime =
-                            msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
-                    out += android::base::StringPrintf("inputEventId=%" PRId32
-                                                       ", gpuCompletedTime=%" PRId64
-                                                       ", presentTime=%" PRId64,
-                                                       msg.body.timeline.eventId, gpuCompletedTime,
-                                                       presentTime);
-                    break;
-                }
-                case InputMessage::Type::TOUCH_MODE: {
-                    out += android::base::StringPrintf("isInTouchMode=%s",
-                                                       toString(msg.body.touchMode.isInTouchMode));
-                    break;
-                }
-            }
-            out += "\n";
-        }
-    }
-    if (mBatches.empty()) {
-        out += "    <empty>\n";
-    }
-    out += "mSeqChains:\n";
-    for (const SeqChain& chain : mSeqChains) {
-        out += android::base::StringPrintf("    chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
-                                           chain.chain);
-    }
-    if (mSeqChains.empty()) {
-        out += "    <empty>\n";
-    }
-    out += "mConsumeTimes:\n";
-    for (const auto& [seq, consumeTime] : mConsumeTimes) {
-        out += android::base::StringPrintf("    seq = %" PRIu32 " consumeTime = %" PRId64, seq,
-                                           consumeTime);
-    }
-    if (mConsumeTimes.empty()) {
-        out += "    <empty>\n";
-    }
-    return out;
-}
-
 } // namespace android
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index e2feabc..b0563ab 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -19,16 +19,13 @@
 #include <stdlib.h>
 #include <string.h>
 
-#ifdef __linux__
-#include <binder/Parcel.h>
-#endif
 #include <android/keycodes.h>
 #include <attestation/HmacKeyManager.h>
+#include <binder/Parcel.h>
 #include <input/InputEventLabels.h>
 #include <input/KeyCharacterMap.h>
 #include <input/Keyboard.h>
 
-#include <gui/constants.h>
 #include <utils/Errors.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
@@ -320,19 +317,8 @@
     return true;
 }
 
-void KeyCharacterMap::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) {
-    if (fromKeyCode == toKeyCode) {
-        mKeyRemapping.erase(fromKeyCode);
-#if DEBUG_MAPPING
-        ALOGD("addKeyRemapping: Cleared remapping forKeyCode=%d ~ Result Successful.", fromKeyCode);
-#endif
-        return;
-    }
-    mKeyRemapping.insert_or_assign(fromKeyCode, toKeyCode);
-#if DEBUG_MAPPING
-    ALOGD("addKeyRemapping: fromKeyCode=%d, toKeyCode=%d ~ Result Successful.", fromKeyCode,
-          toKeyCode);
-#endif
+void KeyCharacterMap::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) {
+    mKeyRemapping = keyRemapping;
 }
 
 status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
@@ -496,13 +482,14 @@
     return false;
 }
 
-void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents,
-        int32_t deviceId, int32_t keyCode, int32_t metaState, bool down, nsecs_t time) {
+void KeyCharacterMap::addKey(Vector<KeyEvent>& outEvents, int32_t deviceId, int32_t keyCode,
+                             int32_t metaState, bool down, nsecs_t time) {
     outEvents.push();
     KeyEvent& event = outEvents.editTop();
-    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode,
-                     0, metaState, 0, time, time);
+    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC,
+                     down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, 0, keyCode, 0, metaState,
+                     0, time, time);
 }
 
 void KeyCharacterMap::addMetaKeys(Vector<KeyEvent>& outEvents,
@@ -612,7 +599,6 @@
     }
 }
 
-#ifdef __linux__
 std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
     if (parcel == nullptr) {
         ALOGE("%s: Null parcel", __func__);
@@ -745,7 +731,6 @@
         parcel->writeInt32(toAndroidKeyCode);
     }
 }
-#endif // __linux__
 
 // --- KeyCharacterMap::Parser ---
 
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index 5088188..f3241c9 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -240,8 +240,9 @@
 
 std::vector<int32_t> KeyLayoutMap::findScanCodesForKey(int32_t keyCode) const {
     std::vector<int32_t> scanCodes;
+    // b/354333072: Only consider keys without FUNCTION flag
     for (const auto& [scanCode, key] : mKeysByScanCode) {
-        if (keyCode == key.keyCode) {
+        if (keyCode == key.keyCode && !(key.flags & POLICY_FLAG_FUNCTION)) {
             scanCodes.push_back(scanCode);
         }
     }
diff --git a/libs/input/KeyboardClassifier.cpp b/libs/input/KeyboardClassifier.cpp
new file mode 100644
index 0000000..0c2c7be
--- /dev/null
+++ b/libs/input/KeyboardClassifier.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "KeyboardClassifier"
+
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <ftl/flags.h>
+#include <input/KeyboardClassifier.h>
+
+#include "input_cxx_bridge.rs.h"
+
+namespace input_flags = com::android::input::flags;
+
+using android::input::RustInputDeviceIdentifier;
+
+namespace android {
+
+KeyboardClassifier::KeyboardClassifier() {
+    if (input_flags::enable_keyboard_classifier()) {
+        mRustClassifier = android::input::keyboardClassifier::create();
+    }
+}
+
+KeyboardType KeyboardClassifier::getKeyboardType(DeviceId deviceId) {
+    if (mRustClassifier) {
+        return static_cast<KeyboardType>(
+                android::input::keyboardClassifier::getKeyboardType(**mRustClassifier, deviceId));
+    } else {
+        auto it = mKeyboardTypeMap.find(deviceId);
+        if (it == mKeyboardTypeMap.end()) {
+            return KeyboardType::NONE;
+        }
+        return it->second;
+    }
+}
+
+// Copied from EventHub.h
+const uint32_t DEVICE_CLASS_KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD;
+const uint32_t DEVICE_CLASS_ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY;
+
+void KeyboardClassifier::notifyKeyboardChanged(DeviceId deviceId,
+                                               const InputDeviceIdentifier& identifier,
+                                               uint32_t deviceClasses) {
+    if (mRustClassifier) {
+        RustInputDeviceIdentifier rustIdentifier;
+        rustIdentifier.name = identifier.name;
+        rustIdentifier.location = identifier.location;
+        rustIdentifier.unique_id = identifier.uniqueId;
+        rustIdentifier.bus = identifier.bus;
+        rustIdentifier.vendor = identifier.vendor;
+        rustIdentifier.product = identifier.product;
+        rustIdentifier.version = identifier.version;
+        rustIdentifier.descriptor = identifier.descriptor;
+        android::input::keyboardClassifier::notifyKeyboardChanged(**mRustClassifier, deviceId,
+                                                                  rustIdentifier, deviceClasses);
+    } else {
+        bool isKeyboard = (deviceClasses & DEVICE_CLASS_KEYBOARD) != 0;
+        bool hasAlphabeticKey = (deviceClasses & DEVICE_CLASS_ALPHAKEY) != 0;
+        mKeyboardTypeMap.insert_or_assign(deviceId,
+                                          isKeyboard ? (hasAlphabeticKey
+                                                                ? KeyboardType::ALPHABETIC
+                                                                : KeyboardType::NON_ALPHABETIC)
+                                                     : KeyboardType::NONE);
+    }
+}
+
+void KeyboardClassifier::processKey(DeviceId deviceId, int32_t evdevCode, uint32_t metaState) {
+    if (mRustClassifier &&
+        !android::input::keyboardClassifier::isFinalized(**mRustClassifier, deviceId)) {
+        android::input::keyboardClassifier::processKey(**mRustClassifier, deviceId, evdevCode,
+                                                       metaState);
+    }
+}
+
+} // namespace android
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index c4e3ff6..c61d394 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -18,21 +18,29 @@
 
 #include <input/MotionPredictor.h>
 
+#include <algorithm>
+#include <array>
 #include <cinttypes>
 #include <cmath>
 #include <cstddef>
 #include <cstdint>
+#include <limits>
+#include <optional>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android-base/logging.h>
 #include <android-base/strings.h>
 #include <android/input.h>
+#include <com_android_input_flags.h>
 
 #include <attestation/HmacKeyManager.h>
 #include <ftl/enum.h>
 #include <input/TfLiteMotionPredictor.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 namespace {
 
@@ -55,8 +63,87 @@
     return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
 }
 
+float normalizeRange(float x, float min, float max) {
+    const float normalized = (x - min) / (max - min);
+    return std::min(1.0f, std::max(0.0f, normalized));
+}
+
 } // namespace
 
+// --- JerkTracker ---
+
+JerkTracker::JerkTracker(bool normalizedDt, float alpha)
+      : mNormalizedDt(normalizedDt), mAlpha(alpha) {}
+
+void JerkTracker::pushSample(int64_t timestamp, float xPos, float yPos) {
+    // If we previously had full samples, we have a previous jerk calculation
+    // to do weighted smoothing.
+    const bool applySmoothing = mTimestamps.size() == mTimestamps.capacity();
+    mTimestamps.pushBack(timestamp);
+    const int numSamples = mTimestamps.size();
+
+    std::array<float, 4> newXDerivatives;
+    std::array<float, 4> newYDerivatives;
+
+    /**
+     * Diagram showing the calculation of higher order derivatives of sample x3
+     * collected at time=t3.
+     * Terms in parentheses are not stored (and not needed for calculations)
+     *  t0 ----- t1  ----- t2 ----- t3
+     * (x0)-----(x1) ----- x2 ----- x3
+     * (x'0) --- x'1 ---  x'2
+     *  x''0  -  x''1
+     *  x'''0
+     *
+     * In this example:
+     * x'2 = (x3 - x2) / (t3 - t2)
+     * x''1 = (x'2 - x'1) / (t2 - t1)
+     * x'''0 = (x''1 - x''0) / (t1 - t0)
+     * Therefore, timestamp history is needed to calculate higher order derivatives,
+     * compared to just the last calculated derivative sample.
+     *
+     * If mNormalizedDt = true, then dt = 1 and the division is moot.
+     */
+    for (int i = 0; i < numSamples; ++i) {
+        if (i == 0) {
+            newXDerivatives[i] = xPos;
+            newYDerivatives[i] = yPos;
+        } else {
+            newXDerivatives[i] = newXDerivatives[i - 1] - mXDerivatives[i - 1];
+            newYDerivatives[i] = newYDerivatives[i - 1] - mYDerivatives[i - 1];
+            if (!mNormalizedDt) {
+                const float dt = mTimestamps[numSamples - i] - mTimestamps[numSamples - i - 1];
+                newXDerivatives[i] = newXDerivatives[i] / dt;
+                newYDerivatives[i] = newYDerivatives[i] / dt;
+            }
+        }
+    }
+
+    if (numSamples == static_cast<int>(mTimestamps.capacity())) {
+        float newJerkMagnitude = std::hypot(newXDerivatives[3], newYDerivatives[3]);
+        ALOGD_IF(isDebug(), "raw jerk: %f", newJerkMagnitude);
+        if (applySmoothing) {
+            mJerkMagnitude = mJerkMagnitude + (mAlpha * (newJerkMagnitude - mJerkMagnitude));
+        } else {
+            mJerkMagnitude = newJerkMagnitude;
+        }
+    }
+
+    std::swap(newXDerivatives, mXDerivatives);
+    std::swap(newYDerivatives, mYDerivatives);
+}
+
+void JerkTracker::reset() {
+    mTimestamps.clear();
+}
+
+std::optional<float> JerkTracker::jerkMagnitude() const {
+    if (mTimestamps.size() == mTimestamps.capacity()) {
+        return mJerkMagnitude;
+    }
+    return std::nullopt;
+}
+
 // --- MotionPredictor ---
 
 MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
@@ -66,6 +153,24 @@
         mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
         mReportAtomFunction(reportAtomFunction) {}
 
+void MotionPredictor::initializeObjects() {
+    mModel = TfLiteMotionPredictorModel::create();
+    LOG_ALWAYS_FATAL_IF(!mModel);
+
+    // mJerkTracker assumes normalized dt = 1 between recorded samples because
+    // the underlying mModel input also assumes fixed-interval samples.
+    // Normalized dt as 1 is also used to correspond with the similar Jank
+    // implementation from the JetPack MotionPredictor implementation.
+    mJerkTracker = std::make_unique<JerkTracker>(/*normalizedDt=*/true, mModel->config().jerkAlpha);
+
+    mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
+
+    mMetricsManager =
+            std::make_unique<MotionPredictorMetricsManager>(mModel->config().predictionInterval,
+                                                            mModel->outputLength(),
+                                                            mReportAtomFunction);
+}
+
 android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
     if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
         // We still have an active gesture for another device. The provided MotionEvent is not
@@ -82,27 +187,18 @@
         return {};
     }
 
-    // Initialise the model now that it's likely to be used.
     if (!mModel) {
-        mModel = TfLiteMotionPredictorModel::create();
-        LOG_ALWAYS_FATAL_IF(!mModel);
-    }
-
-    if (!mBuffers) {
-        mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
+        initializeObjects();
     }
 
     // Pass input event to the MetricsManager.
-    if (!mMetricsManager) {
-        mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength(),
-                                mReportAtomFunction);
-    }
     mMetricsManager->onRecord(event);
 
     const int32_t action = event.getActionMasked();
     if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
         ALOGD_IF(isDebug(), "End of event stream");
         mBuffers->reset();
+        mJerkTracker->reset();
         mLastEvent.reset();
         return {};
     } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
@@ -137,6 +233,9 @@
                                                                           0, i),
                                      .orientation = event.getHistoricalOrientation(0, i),
                              });
+        mJerkTracker->pushSample(event.getHistoricalEventTime(i),
+                                 coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+                                 coords->getAxisValue(AMOTION_EVENT_AXIS_Y));
     }
 
     if (!mLastEvent) {
@@ -184,6 +283,17 @@
     int64_t predictionTime = mBuffers->lastTimestamp();
     const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
 
+    const float jerkMagnitude = mJerkTracker->jerkMagnitude().value_or(0);
+    const float fractionKept =
+            1 - normalizeRange(jerkMagnitude, mModel->config().lowJerk, mModel->config().highJerk);
+    // float to ensure proper division below.
+    const float predictionTimeWindow = futureTime - predictionTime;
+    const int maxNumPredictions = static_cast<int>(
+            std::ceil(predictionTimeWindow / mModel->config().predictionInterval * fractionKept));
+    ALOGD_IF(isDebug(),
+             "jerk (d^3p/normalizedDt^3): %f, fraction of prediction window pruned: %f, max number "
+             "of predictions: %d",
+             jerkMagnitude, 1 - fractionKept, maxNumPredictions);
     for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime;
          ++i) {
         if (predictedR[i] < mModel->config().distanceNoiseFloor) {
@@ -197,7 +307,13 @@
             // device starts to speed up, but avoids producing noisy predictions as it slows down.
             break;
         }
-        // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+        if (input_flags::enable_prediction_pruning_via_jerk_thresholding()) {
+            if (i >= static_cast<size_t>(maxNumPredictions)) {
+                break;
+            }
+        }
+        // TODO(b/266747654): Stop predictions if confidence is < some
+        // threshold. Currently predictions are pruned via jerk thresholding.
 
         const TfLiteMotionPredictorSample::Point predictedPoint =
                 convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
@@ -229,7 +345,7 @@
                                    event.getRawTransform(), event.getDownTime(), predictionTime,
                                    event.getPointerCount(), event.getPointerProperties(), &coords);
         } else {
-            prediction->addSample(predictionTime, &coords);
+            prediction->addSample(predictionTime, &coords, prediction->getId());
         }
 
         axisFrom = axisTo;
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
index 0412d08..ccf018e 100644
--- a/libs/input/MotionPredictorMetricsManager.cpp
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -21,14 +21,13 @@
 #include <algorithm>
 
 #include <android-base/logging.h>
+#ifdef __ANDROID__
+#include <statslog_libinput.h>
+#endif // __ANDROID__
 
 #include "Eigen/Core"
 #include "Eigen/Geometry"
 
-#ifdef __ANDROID__
-#include <statslog_libinput.h>
-#endif
-
 namespace android {
 namespace {
 
@@ -48,22 +47,20 @@
 
 void MotionPredictorMetricsManager::defaultReportAtomFunction(
         const MotionPredictorMetricsManager::AtomFields& atomFields) {
-    // Call stats_write logging function only on Android targets (not supported on host).
 #ifdef __ANDROID__
-    android::stats::libinput::
-            stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
-                            /*stylus_vendor_id=*/0,
-                            /*stylus_product_id=*/0,
-                            atomFields.deltaTimeBucketMilliseconds,
-                            atomFields.alongTrajectoryErrorMeanMillipixels,
-                            atomFields.alongTrajectoryErrorStdMillipixels,
-                            atomFields.offTrajectoryRmseMillipixels,
-                            atomFields.pressureRmseMilliunits,
-                            atomFields.highVelocityAlongTrajectoryRmse,
-                            atomFields.highVelocityOffTrajectoryRmse,
-                            atomFields.scaleInvariantAlongTrajectoryRmse,
-                            atomFields.scaleInvariantOffTrajectoryRmse);
-#endif
+    android::libinput::stats_write(android::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+                                   /*stylus_vendor_id=*/0,
+                                   /*stylus_product_id=*/0,
+                                   atomFields.deltaTimeBucketMilliseconds,
+                                   atomFields.alongTrajectoryErrorMeanMillipixels,
+                                   atomFields.alongTrajectoryErrorStdMillipixels,
+                                   atomFields.offTrajectoryRmseMillipixels,
+                                   atomFields.pressureRmseMilliunits,
+                                   atomFields.highVelocityAlongTrajectoryRmse,
+                                   atomFields.highVelocityOffTrajectoryRmse,
+                                   atomFields.scaleInvariantAlongTrajectoryRmse,
+                                   atomFields.scaleInvariantOffTrajectoryRmse);
+#endif // __ANDROID__
 }
 
 MotionPredictorMetricsManager::MotionPredictorMetricsManager(
@@ -113,7 +110,12 @@
 // Adds new predictions to mRecentPredictions and maintains the invariant that elements are
 // sorted in ascending order of targetTimestamp.
 void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) {
-    for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) {
+    const size_t numPredictions = predictionEvent.getHistorySize() + 1;
+    if (numPredictions > mMaxNumPredictions) {
+        LOG(WARNING) << "numPredictions (" << numPredictions << ") > mMaxNumPredictions ("
+                     << mMaxNumPredictions << "). Ignoring extra predictions in metrics.";
+    }
+    for (size_t i = 0; (i < numPredictions) && (i < mMaxNumPredictions); ++i) {
         // Convert MotionEvent to PredictionPoint.
         const PointerCoords* coords =
                 predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i);
@@ -325,42 +327,44 @@
             mAtomFields[i].highVelocityOffTrajectoryRmse =
                     static_cast<int>(offTrajectoryRmse * 1000);
         }
+    }
 
-        // Scale-invariant errors: reported only for the last time bucket, where the values
-        // represent an average across all time buckets.
-        if (i + 1 == mMaxNumPredictions) {
-            // Compute error averages.
-            float alongTrajectoryRmseSum = 0;
-            float offTrajectoryRmseSum = 0;
-            for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
-                // If we have general errors (checked above), we should always also have
-                // scale-invariant errors.
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
-                alongTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
-
-                LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
-                                    "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
-                                    "should not be negative",
-                                    j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
-                offTrajectoryRmseSum +=
-                        std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
-                                  mAggregatedMetrics[j].scaleInvariantErrorsCount);
+    // Scale-invariant errors: the average scale-invariant error across all time buckets
+    // is reported in the last time bucket.
+    {
+        // Compute error averages.
+        float alongTrajectoryRmseSum = 0;
+        float offTrajectoryRmseSum = 0;
+        int bucket_count = 0;
+        for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
+            if (mAggregatedMetrics[j].scaleInvariantErrorsCount == 0) {
+                continue;
             }
 
-            const float averageAlongTrajectoryRmse =
-                    alongTrajectoryRmseSum / mAggregatedMetrics.size();
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
+            alongTrajectoryRmseSum +=
+                    std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
+                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
+                                "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
+                                "should not be negative",
+                                j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
+            offTrajectoryRmseSum += std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
+                                              mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+            ++bucket_count;
+        }
+
+        if (bucket_count > 0) {
+            const float averageAlongTrajectoryRmse = alongTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantAlongTrajectoryRmse =
                     static_cast<int>(averageAlongTrajectoryRmse * 1000);
 
-            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size();
+            const float averageOffTrajectoryRmse = offTrajectoryRmseSum / bucket_count;
             mAtomFields.back().scaleInvariantOffTrajectoryRmse =
                     static_cast<int>(averageOffTrajectoryRmse * 1000);
         }
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
new file mode 100644
index 0000000..51fadf8
--- /dev/null
+++ b/libs/input/Resampler.cpp
@@ -0,0 +1,266 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LegacyResampler"
+
+#include <algorithm>
+#include <chrono>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <ftl/enum.h>
+
+#include <input/Resampler.h>
+#include <utils/Timers.h>
+
+using std::chrono::nanoseconds;
+
+namespace android {
+
+namespace {
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+        android::base::GetBoolProperty("ro.debuggable", false);
+#else
+        true;
+#endif
+
+bool debugResampling() {
+    if (!IS_DEBUGGABLE_BUILD) {
+        static const bool DEBUG_TRANSPORT_RESAMPLING =
+                __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+                                          ANDROID_LOG_INFO);
+        return DEBUG_TRANSPORT_RESAMPLING;
+    }
+    return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+constexpr std::chrono::milliseconds RESAMPLE_LATENCY{5};
+
+constexpr std::chrono::milliseconds RESAMPLE_MIN_DELTA{2};
+
+constexpr std::chrono::milliseconds RESAMPLE_MAX_DELTA{20};
+
+constexpr std::chrono::milliseconds RESAMPLE_MAX_PREDICTION{8};
+
+bool canResampleTool(ToolType toolType) {
+    return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
+            toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
+}
+
+inline float lerp(float a, float b, float alpha) {
+    return a + alpha * (b - a);
+}
+
+PointerCoords calculateResampledCoords(const PointerCoords& a, const PointerCoords& b,
+                                       float alpha) {
+    // We use the value of alpha to initialize resampledCoords with the latest sample information.
+    PointerCoords resampledCoords = (alpha < 1.0f) ? a : b;
+    resampledCoords.isResampled = true;
+    resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X, lerp(a.getX(), b.getX(), alpha));
+    resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, lerp(a.getY(), b.getY(), alpha));
+    return resampledCoords;
+}
+} // namespace
+
+void LegacyResampler::updateLatestSamples(const MotionEvent& motionEvent) {
+    const size_t numSamples = motionEvent.getHistorySize() + 1;
+    const size_t latestIndex = numSamples - 1;
+    const size_t secondToLatestIndex = (latestIndex > 0) ? (latestIndex - 1) : 0;
+    for (size_t sampleIndex = secondToLatestIndex; sampleIndex < numSamples; ++sampleIndex) {
+        std::vector<Pointer> pointers;
+        const size_t numPointers = motionEvent.getPointerCount();
+        for (size_t pointerIndex = 0; pointerIndex < numPointers; ++pointerIndex) {
+            // getSamplePointerCoords is the vector representation of a getHistorySize by
+            // getPointerCount matrix.
+            const PointerCoords& pointerCoords =
+                    motionEvent.getSamplePointerCoords()[sampleIndex * numPointers + pointerIndex];
+            pointers.push_back(
+                    Pointer{*motionEvent.getPointerProperties(pointerIndex), pointerCoords});
+        }
+        mLatestSamples.pushBack(
+                Sample{nanoseconds{motionEvent.getHistoricalEventTime(sampleIndex)}, pointers});
+    }
+}
+
+LegacyResampler::Sample LegacyResampler::messageToSample(const InputMessage& message) {
+    std::vector<Pointer> pointers;
+    for (uint32_t i = 0; i < message.body.motion.pointerCount; ++i) {
+        pointers.push_back(Pointer{message.body.motion.pointers[i].properties,
+                                   message.body.motion.pointers[i].coords});
+    }
+    return Sample{nanoseconds{message.body.motion.eventTime}, pointers};
+}
+
+bool LegacyResampler::pointerPropertiesResampleable(const Sample& target, const Sample& auxiliary) {
+    if (target.pointers.size() > auxiliary.pointers.size()) {
+        LOG_IF(INFO, debugResampling())
+                << "Not resampled. Auxiliary sample has fewer pointers than target sample.";
+        return false;
+    }
+    for (size_t i = 0; i < target.pointers.size(); ++i) {
+        if (target.pointers[i].properties.id != auxiliary.pointers[i].properties.id) {
+            LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ID mismatch.";
+            return false;
+        }
+        if (target.pointers[i].properties.toolType != auxiliary.pointers[i].properties.toolType) {
+            LOG_IF(INFO, debugResampling()) << "Not resampled. Pointer ToolType mismatch.";
+            return false;
+        }
+        if (!canResampleTool(target.pointers[i].properties.toolType)) {
+            LOG_IF(INFO, debugResampling())
+                    << "Not resampled. Cannot resample "
+                    << ftl::enum_string(target.pointers[i].properties.toolType) << " ToolType.";
+            return false;
+        }
+    }
+    return true;
+}
+
+bool LegacyResampler::canInterpolate(const InputMessage& message) const {
+    LOG_IF(FATAL, mLatestSamples.empty())
+            << "Not resampled. mLatestSamples must not be empty to interpolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 1);
+    const Sample& futureSample = messageToSample(message);
+
+    if (!pointerPropertiesResampleable(pastSample, futureSample)) {
+        return false;
+    }
+
+    const nanoseconds delta = futureSample.eventTime - pastSample.eventTime;
+    if (delta < RESAMPLE_MIN_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        return false;
+    }
+    return true;
+}
+
+std::optional<LegacyResampler::Sample> LegacyResampler::attemptInterpolation(
+        nanoseconds resampleTime, const InputMessage& futureSample) const {
+    if (!canInterpolate(futureSample)) {
+        return std::nullopt;
+    }
+    LOG_IF(FATAL, mLatestSamples.empty())
+            << "Not resampled. mLatestSamples must not be empty to interpolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 1);
+
+    const nanoseconds delta =
+            nanoseconds{futureSample.body.motion.eventTime} - pastSample.eventTime;
+    const float alpha =
+            std::chrono::duration<float, std::milli>(resampleTime - pastSample.eventTime) / delta;
+
+    std::vector<Pointer> resampledPointers;
+    for (size_t i = 0; i < pastSample.pointers.size(); ++i) {
+        const PointerCoords& resampledCoords =
+                calculateResampledCoords(pastSample.pointers[i].coords,
+                                         futureSample.body.motion.pointers[i].coords, alpha);
+        resampledPointers.push_back(Pointer{pastSample.pointers[i].properties, resampledCoords});
+    }
+    return Sample{resampleTime, resampledPointers};
+}
+
+bool LegacyResampler::canExtrapolate() const {
+    if (mLatestSamples.size() < 2) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Not enough data.";
+        return false;
+    }
+
+    const Sample& pastSample = *(mLatestSamples.end() - 2);
+    const Sample& presentSample = *(mLatestSamples.end() - 1);
+
+    if (!pointerPropertiesResampleable(presentSample, pastSample)) {
+        return false;
+    }
+
+    const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
+    if (delta < RESAMPLE_MIN_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too small: " << delta << "ns.";
+        return false;
+    } else if (delta > RESAMPLE_MAX_DELTA) {
+        LOG_IF(INFO, debugResampling()) << "Not resampled. Delta is too large: " << delta << "ns.";
+        return false;
+    }
+    return true;
+}
+
+std::optional<LegacyResampler::Sample> LegacyResampler::attemptExtrapolation(
+        nanoseconds resampleTime) const {
+    if (!canExtrapolate()) {
+        return std::nullopt;
+    }
+    LOG_IF(FATAL, mLatestSamples.size() < 2)
+            << "Not resampled. mLatestSamples must have at least two samples to extrapolate.";
+
+    const Sample& pastSample = *(mLatestSamples.end() - 2);
+    const Sample& presentSample = *(mLatestSamples.end() - 1);
+
+    const nanoseconds delta = presentSample.eventTime - pastSample.eventTime;
+    // The farthest future time to which we can extrapolate. If the given resampleTime exceeds this,
+    // we use this value as the resample time target.
+    const nanoseconds farthestPrediction =
+            presentSample.eventTime + std::min<nanoseconds>(delta / 2, RESAMPLE_MAX_PREDICTION);
+    const nanoseconds newResampleTime =
+            (resampleTime > farthestPrediction) ? (farthestPrediction) : (resampleTime);
+    LOG_IF(INFO, debugResampling() && newResampleTime == farthestPrediction)
+            << "Resample time is too far in the future. Adjusting prediction from "
+            << (resampleTime - presentSample.eventTime) << " to "
+            << (farthestPrediction - presentSample.eventTime) << "ns.";
+    const float alpha =
+            std::chrono::duration<float, std::milli>(newResampleTime - pastSample.eventTime) /
+            delta;
+
+    std::vector<Pointer> resampledPointers;
+    for (size_t i = 0; i < presentSample.pointers.size(); ++i) {
+        const PointerCoords& resampledCoords =
+                calculateResampledCoords(pastSample.pointers[i].coords,
+                                         presentSample.pointers[i].coords, alpha);
+        resampledPointers.push_back(Pointer{presentSample.pointers[i].properties, resampledCoords});
+    }
+    return Sample{newResampleTime, resampledPointers};
+}
+
+inline void LegacyResampler::addSampleToMotionEvent(const Sample& sample,
+                                                    MotionEvent& motionEvent) {
+    motionEvent.addSample(sample.eventTime.count(), sample.asPointerCoords().data(),
+                          motionEvent.getId());
+}
+
+nanoseconds LegacyResampler::getResampleLatency() const {
+    return RESAMPLE_LATENCY;
+}
+
+void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
+                                          const InputMessage* futureSample) {
+    if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
+        mLatestSamples.clear();
+    }
+    mPreviousDeviceId = motionEvent.getDeviceId();
+
+    const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
+
+    updateLatestSamples(motionEvent);
+
+    const std::optional<Sample> sample = (futureSample != nullptr)
+            ? (attemptInterpolation(resampleTime, *futureSample))
+            : (attemptExtrapolation(resampleTime));
+    if (sample.has_value()) {
+        addSampleToMotionEvent(*sample, motionEvent);
+    }
+}
+} // namespace android
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
index d17476e..5250a9d 100644
--- a/libs/input/TfLiteMotionPredictor.cpp
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -281,6 +281,9 @@
     Config config{
             .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"),
             .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"),
+            .lowJerk = parseXMLFloat(*configRoot, "low-jerk"),
+            .highJerk = parseXMLFloat(*configRoot, "high-jerk"),
+            .jerkAlpha = parseXMLFloat(*configRoot, "jerk-alpha"),
     };
 
     return std::unique_ptr<TfLiteMotionPredictorModel>(
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index eea06f1..51edbf1 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -16,30 +16,267 @@
 
 #define LOG_TAG "VirtualInputDevice"
 
+#include <android-base/logging.h>
 #include <android/input.h>
 #include <android/keycodes.h>
+#include <android_companion_virtualdevice_flags.h>
 #include <fcntl.h>
 #include <input/Input.h>
 #include <input/VirtualInputDevice.h>
 #include <linux/uinput.h>
-#include <math.h>
-#include <utils/Log.h>
 
-#include <map>
 #include <string>
 
 using android::base::unique_fd;
 
+namespace {
+
 /**
  * Log debug messages about native virtual input devices.
  * Enable this via "adb shell setprop log.tag.VirtualInputDevice DEBUG"
  */
-static bool isDebug() {
+bool isDebug() {
     return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
 }
 
+unique_fd invalidFd() {
+    return unique_fd(-1);
+}
+
+} // namespace
+
 namespace android {
 
+namespace vd_flags = android::companion::virtualdevice::flags;
+
+/** Creates a new uinput device and assigns a file descriptor. */
+unique_fd openUinput(const char* readableName, int32_t vendorId, int32_t productId,
+                     const char* phys, DeviceType deviceType, int32_t screenHeight,
+                     int32_t screenWidth) {
+    unique_fd fd(TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK)));
+    if (fd < 0) {
+        ALOGE("Error creating uinput device: %s", strerror(errno));
+        return invalidFd();
+    }
+
+    ioctl(fd, UI_SET_PHYS, phys);
+
+    ioctl(fd, UI_SET_EVBIT, EV_KEY);
+    ioctl(fd, UI_SET_EVBIT, EV_SYN);
+    switch (deviceType) {
+        case DeviceType::DPAD:
+            for (const auto& [_, keyCode] : VirtualDpad::DPAD_KEY_CODE_MAPPING) {
+                ioctl(fd, UI_SET_KEYBIT, keyCode);
+            }
+            break;
+        case DeviceType::KEYBOARD:
+            for (const auto& [_, keyCode] : VirtualKeyboard::KEY_CODE_MAPPING) {
+                ioctl(fd, UI_SET_KEYBIT, keyCode);
+            }
+            break;
+        case DeviceType::MOUSE:
+            ioctl(fd, UI_SET_EVBIT, EV_REL);
+            ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
+            ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);
+            ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);
+            ioctl(fd, UI_SET_KEYBIT, BTN_BACK);
+            ioctl(fd, UI_SET_KEYBIT, BTN_FORWARD);
+            ioctl(fd, UI_SET_RELBIT, REL_X);
+            ioctl(fd, UI_SET_RELBIT, REL_Y);
+            ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+            ioctl(fd, UI_SET_RELBIT, REL_HWHEEL);
+            if (vd_flags::high_resolution_scroll()) {
+                ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
+                ioctl(fd, UI_SET_RELBIT, REL_HWHEEL_HI_RES);
+            }
+            break;
+        case DeviceType::TOUCHSCREEN:
+            ioctl(fd, UI_SET_EVBIT, EV_ABS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOUCH_MAJOR);
+            ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
+            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+            break;
+        case DeviceType::STYLUS:
+            ioctl(fd, UI_SET_EVBIT, EV_ABS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS);
+            ioctl(fd, UI_SET_KEYBIT, BTN_STYLUS2);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_PEN);
+            ioctl(fd, UI_SET_KEYBIT, BTN_TOOL_RUBBER);
+            ioctl(fd, UI_SET_ABSBIT, ABS_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_X);
+            ioctl(fd, UI_SET_ABSBIT, ABS_TILT_Y);
+            ioctl(fd, UI_SET_ABSBIT, ABS_PRESSURE);
+            ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
+            break;
+        case DeviceType::ROTARY_ENCODER:
+            ioctl(fd, UI_SET_EVBIT, EV_REL);
+            ioctl(fd, UI_SET_RELBIT, REL_WHEEL);
+            if (vd_flags::high_resolution_scroll()) {
+                ioctl(fd, UI_SET_RELBIT, REL_WHEEL_HI_RES);
+            }
+            break;
+        default:
+            ALOGE("Invalid input device type %d", static_cast<int32_t>(deviceType));
+            return invalidFd();
+    }
+
+    int version;
+    if (ioctl(fd, UI_GET_VERSION, &version) == 0 && version >= 5) {
+        uinput_setup setup;
+        memset(&setup, 0, sizeof(setup));
+        std::strncpy(setup.name, readableName, UINPUT_MAX_NAME_SIZE);
+        setup.id.version = 1;
+        setup.id.bustype = BUS_VIRTUAL;
+        setup.id.vendor = vendorId;
+        setup.id.product = productId;
+        if (deviceType == DeviceType::TOUCHSCREEN) {
+            uinput_abs_setup xAbsSetup;
+            xAbsSetup.code = ABS_MT_POSITION_X;
+            xAbsSetup.absinfo.maximum = screenWidth - 1;
+            xAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput x axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup yAbsSetup;
+            yAbsSetup.code = ABS_MT_POSITION_Y;
+            yAbsSetup.absinfo.maximum = screenHeight - 1;
+            yAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput y axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup majorAbsSetup;
+            majorAbsSetup.code = ABS_MT_TOUCH_MAJOR;
+            majorAbsSetup.absinfo.maximum = screenWidth - 1;
+            majorAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &majorAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput major axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup pressureAbsSetup;
+            pressureAbsSetup.code = ABS_MT_PRESSURE;
+            pressureAbsSetup.absinfo.maximum = 255;
+            pressureAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup slotAbsSetup;
+            slotAbsSetup.code = ABS_MT_SLOT;
+            slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
+            slotAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup trackingIdAbsSetup;
+            trackingIdAbsSetup.code = ABS_MT_TRACKING_ID;
+            trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
+            trackingIdAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
+                return invalidFd();
+            }
+        } else if (deviceType == DeviceType::STYLUS) {
+            uinput_abs_setup xAbsSetup;
+            xAbsSetup.code = ABS_X;
+            xAbsSetup.absinfo.maximum = screenWidth - 1;
+            xAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &xAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput x axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup yAbsSetup;
+            yAbsSetup.code = ABS_Y;
+            yAbsSetup.absinfo.maximum = screenHeight - 1;
+            yAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &yAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput y axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup tiltXAbsSetup;
+            tiltXAbsSetup.code = ABS_TILT_X;
+            tiltXAbsSetup.absinfo.maximum = 90;
+            tiltXAbsSetup.absinfo.minimum = -90;
+            if (ioctl(fd, UI_ABS_SETUP, &tiltXAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput tilt x axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup tiltYAbsSetup;
+            tiltYAbsSetup.code = ABS_TILT_Y;
+            tiltYAbsSetup.absinfo.maximum = 90;
+            tiltYAbsSetup.absinfo.minimum = -90;
+            if (ioctl(fd, UI_ABS_SETUP, &tiltYAbsSetup) != 0) {
+                ALOGE("Error creating stylus uinput tilt y axis: %s", strerror(errno));
+                return invalidFd();
+            }
+            uinput_abs_setup pressureAbsSetup;
+            pressureAbsSetup.code = ABS_PRESSURE;
+            pressureAbsSetup.absinfo.maximum = 255;
+            pressureAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &pressureAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput pressure axis: %s", strerror(errno));
+                return invalidFd();
+            }
+        }
+        if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
+            ALOGE("Error creating uinput device: %s", strerror(errno));
+            return invalidFd();
+        }
+    } else {
+        // UI_DEV_SETUP was not introduced until version 5. Try setting up manually.
+        ALOGI("Falling back to version %d manual setup", version);
+        uinput_user_dev fallback;
+        memset(&fallback, 0, sizeof(fallback));
+        std::strncpy(fallback.name, readableName, UINPUT_MAX_NAME_SIZE);
+        fallback.id.version = 1;
+        fallback.id.bustype = BUS_VIRTUAL;
+        fallback.id.vendor = vendorId;
+        fallback.id.product = productId;
+        if (deviceType == DeviceType::TOUCHSCREEN) {
+            fallback.absmin[ABS_MT_POSITION_X] = 0;
+            fallback.absmax[ABS_MT_POSITION_X] = screenWidth - 1;
+            fallback.absmin[ABS_MT_POSITION_Y] = 0;
+            fallback.absmax[ABS_MT_POSITION_Y] = screenHeight - 1;
+            fallback.absmin[ABS_MT_TOUCH_MAJOR] = 0;
+            fallback.absmax[ABS_MT_TOUCH_MAJOR] = screenWidth - 1;
+            fallback.absmin[ABS_MT_PRESSURE] = 0;
+            fallback.absmax[ABS_MT_PRESSURE] = 255;
+        } else if (deviceType == DeviceType::STYLUS) {
+            fallback.absmin[ABS_X] = 0;
+            fallback.absmax[ABS_X] = screenWidth - 1;
+            fallback.absmin[ABS_Y] = 0;
+            fallback.absmax[ABS_Y] = screenHeight - 1;
+            fallback.absmin[ABS_TILT_X] = -90;
+            fallback.absmax[ABS_TILT_X] = 90;
+            fallback.absmin[ABS_TILT_Y] = -90;
+            fallback.absmax[ABS_TILT_Y] = 90;
+            fallback.absmin[ABS_PRESSURE] = 0;
+            fallback.absmax[ABS_PRESSURE] = 255;
+        }
+        if (TEMP_FAILURE_RETRY(write(fd, &fallback, sizeof(fallback))) != sizeof(fallback)) {
+            ALOGE("Error creating uinput device: %s", strerror(errno));
+            return invalidFd();
+        }
+    }
+
+    if (ioctl(fd, UI_DEV_CREATE) != 0) {
+        ALOGE("Error creating uinput device: %s", strerror(errno));
+        return invalidFd();
+    }
+
+    return fd;
+}
+
 VirtualInputDevice::VirtualInputDevice(unique_fd fd) : mFd(std::move(fd)) {}
 
 VirtualInputDevice::~VirtualInputDevice() {
@@ -253,7 +490,10 @@
         // clang-format on
 };
 
-VirtualMouse::VirtualMouse(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
+VirtualMouse::VirtualMouse(unique_fd fd)
+      : VirtualInputDevice(std::move(fd)),
+        mAccumulatedHighResScrollX(0),
+        mAccumulatedHighResScrollY(0) {}
 
 VirtualMouse::~VirtualMouse() {}
 
@@ -272,9 +512,47 @@
 
 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);
+    if (!vd_flags::high_resolution_scroll()) {
+        return writeInputEvent(EV_REL, REL_HWHEEL, static_cast<int32_t>(xAxisMovement),
+                               eventTime) &&
+                writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(yAxisMovement),
+                                eventTime) &&
+                writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+    }
+
+    const auto highResScrollX =
+            static_cast<int32_t>(xAxisMovement * kEvdevHighResScrollUnitsPerDetent);
+    const auto highResScrollY =
+            static_cast<int32_t>(yAxisMovement * kEvdevHighResScrollUnitsPerDetent);
+    bool highResScrollResult =
+            writeInputEvent(EV_REL, REL_HWHEEL_HI_RES, highResScrollX, eventTime) &&
+            writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollY, eventTime);
+    if (!highResScrollResult) {
+        return false;
+    }
+
+    // According to evdev spec, a high-resolution mouse needs to emit REL_WHEEL / REL_HWHEEL events
+    // in addition to high-res scroll events. Regular scroll events can approximate high-res scroll
+    // events, so we send a regular scroll event when the accumulated scroll motion reaches a detent
+    // (single mouse wheel click).
+    mAccumulatedHighResScrollX += highResScrollX;
+    mAccumulatedHighResScrollY += highResScrollY;
+    const int32_t scrollX = mAccumulatedHighResScrollX / kEvdevHighResScrollUnitsPerDetent;
+    const int32_t scrollY = mAccumulatedHighResScrollY / kEvdevHighResScrollUnitsPerDetent;
+    if (scrollX != 0) {
+        if (!writeInputEvent(EV_REL, REL_HWHEEL, scrollX, eventTime)) {
+            return false;
+        }
+        mAccumulatedHighResScrollX %= kEvdevHighResScrollUnitsPerDetent;
+    }
+    if (scrollY != 0) {
+        if (!writeInputEvent(EV_REL, REL_WHEEL, scrollY, eventTime)) {
+            return false;
+        }
+        mAccumulatedHighResScrollY %= kEvdevHighResScrollUnitsPerDetent;
+    }
+
+    return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
 }
 
 // --- VirtualTouchscreen ---
@@ -509,4 +787,39 @@
     return true;
 }
 
+// --- VirtualRotaryEncoder ---
+VirtualRotaryEncoder::VirtualRotaryEncoder(unique_fd fd)
+      : VirtualInputDevice(std::move(fd)), mAccumulatedHighResScrollAmount(0) {}
+
+VirtualRotaryEncoder::~VirtualRotaryEncoder() {}
+
+bool VirtualRotaryEncoder::writeScrollEvent(float scrollAmount,
+                                            std::chrono::nanoseconds eventTime) {
+    if (!vd_flags::high_resolution_scroll()) {
+        return writeInputEvent(EV_REL, REL_WHEEL, static_cast<int32_t>(scrollAmount), eventTime) &&
+                writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+    }
+
+    const auto highResScrollAmount =
+            static_cast<int32_t>(scrollAmount * kEvdevHighResScrollUnitsPerDetent);
+    if (!writeInputEvent(EV_REL, REL_WHEEL_HI_RES, highResScrollAmount, eventTime)) {
+        return false;
+    }
+
+    // According to evdev spec, a high-resolution scroll device needs to emit REL_WHEEL / REL_HWHEEL
+    // events in addition to high-res scroll events. Regular scroll events can approximate high-res
+    // scroll events, so we send a regular scroll event when the accumulated scroll motion reaches a
+    // detent (single wheel click).
+    mAccumulatedHighResScrollAmount += highResScrollAmount;
+    const int32_t scroll = mAccumulatedHighResScrollAmount / kEvdevHighResScrollUnitsPerDetent;
+    if (scroll != 0) {
+        if (!writeInputEvent(EV_REL, REL_WHEEL, scroll, eventTime)) {
+            return false;
+        }
+        mAccumulatedHighResScrollAmount %= kEvdevHighResScrollUnitsPerDetent;
+    }
+
+    return writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime);
+}
+
 } // namespace android
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 8f6f95b..e23fc94 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,12 +49,24 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
-     * The input event was generated or modified by accessibility service.
-     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
-     * set of flags, including in input/Input.h and in android/input.h.
+     * Common input event flag used for both motion and key events for a gesture or pointer being
+     * canceled.
+     */
+    const int INPUT_EVENT_FLAG_CANCELED = 0x20;
+
+    /**
+     * Common input event flag used for both motion and key events, indicating that the event
+     * was generated or modified by accessibility service.
      */
     const int INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT = 0x800;
 
+    /**
+     * Common input event flag used for both motion and key events, indicating that the system has
+     * detected this event may be inconsistent with the current event sequence or gesture, such as
+     * when a pointer move event is sent but the pointer is not down.
+     */
+    const int INPUT_EVENT_FLAG_TAINTED = 0x80000000;
+
     /* The default pointer acceleration value. */
     const int DEFAULT_POINTER_ACCELERATION = 3;
 
@@ -141,4 +153,157 @@
      * time to adjust to changes in direction.
      */
     const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
+
+
+    /*
+     * Input device class: Keyboard
+     * The input device is a keyboard or has buttons.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_KEYBOARD = 0x00000001;
+
+    /*
+     * Input device class: Alphakey
+     * The input device is an alpha-numeric keyboard (not just a dial pad).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_ALPHAKEY = 0x00000002;
+
+    /*
+     * Input device class: Touch
+     * The input device is a touchscreen or a touchpad (either single-touch or multi-touch).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCH = 0x00000004;
+
+    /*
+     * Input device class: Cursor
+     * The input device is a cursor device such as a trackball or mouse.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_CURSOR = 0x00000008;
+
+    /*
+     * Input device class: Multi-touch
+     * The input device is a multi-touch touchscreen or touchpad.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCH_MT = 0x00000010;
+
+    /*
+     * Input device class: Dpad
+     * The input device is a directional pad (implies keyboard, has DPAD keys).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_DPAD = 0x00000020;
+
+    /*
+     * Input device class: Gamepad
+     * The input device is a gamepad (implies keyboard, has BUTTON keys).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_GAMEPAD = 0x00000040;
+
+    /*
+     * Input device class: Switch
+     * The input device has switches.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_SWITCH = 0x00000080;
+
+    /*
+     * Input device class: Joystick
+     * The input device is a joystick (implies gamepad, has joystick absolute axes).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_JOYSTICK = 0x00000100;
+
+    /*
+     * Input device class: Vibrator
+     * The input device has a vibrator (supports FF_RUMBLE).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_VIBRATOR = 0x00000200;
+
+    /*
+     * Input device class: Mic
+     * The input device has a microphone.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_MIC = 0x00000400;
+
+    /*
+     * Input device class: External Stylus
+     * The input device is an external stylus (has data we want to fuse with touch data).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_EXTERNAL_STYLUS = 0x00000800;
+
+    /*
+     * Input device class: Rotary Encoder
+     * The input device has a rotary encoder.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_ROTARY_ENCODER = 0x00001000;
+
+    /*
+     * Input device class: Sensor
+     * The input device has a sensor like accelerometer, gyro, etc.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_SENSOR = 0x00002000;
+
+    /*
+     * Input device class: Battery
+     * The input device has a battery.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_BATTERY = 0x00004000;
+
+    /*
+     * Input device class: Light
+     * The input device has sysfs controllable lights.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_LIGHT = 0x00008000;
+
+    /*
+     * Input device class: Touchpad
+     * The input device is a touchpad, requiring an on-screen cursor.
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_TOUCHPAD = 0x00010000;
+
+    /*
+     * Input device class: Virtual
+     * The input device is virtual (not a real device, not part of UI configuration).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_VIRTUAL = 0x20000000;
+
+    /*
+     * Input device class: External
+     * The input device is external (not built-in).
+     *
+     * @hide
+     */
+    const int DEVICE_CLASS_EXTERNAL = 0x40000000;
 }
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 5d39155..da62e03 100644
--- a/libs/input/android/os/InputConfig.aidl
+++ b/libs/input/android/os/InputConfig.aidl
@@ -157,4 +157,14 @@
      * like StatusBar and TaskBar.
      */
     GLOBAL_STYLUS_BLOCKS_TOUCH   = 1 << 17,
+
+    /**
+     * InputConfig used to indicate that this window is privacy sensitive. This may be used to
+     * redact input interactions from tracing or screen mirroring.
+     *
+     * This must be set on windows that use {@link WindowManager.LayoutParams#FLAG_SECURE},
+     * but it may also be set without setting FLAG_SECURE. The tracing configuration will
+     * determine how these sensitive events are eventually traced.
+     */
+     SENSITIVE_FOR_PRIVACY       = 1 << 18,
 }
diff --git a/libs/input/android/os/MotionEventFlag.aidl b/libs/input/android/os/MotionEventFlag.aidl
new file mode 100644
index 0000000..2093b06
--- /dev/null
+++ b/libs/input/android/os/MotionEventFlag.aidl
@@ -0,0 +1,152 @@
+/**
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.os.IInputConstants;
+
+/**
+ * Flag definitions for MotionEvents.
+ * @hide
+ */
+@Backing(type="int")
+enum MotionEventFlag {
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event directly passed through
+     * the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     */
+    WINDOW_IS_OBSCURED = 0x1,
+
+    /**
+     * This flag indicates that the window that received this motion event is partly
+     * or wholly obscured by another visible window above it and the event did not directly pass
+     * through the obscured area.
+     *
+     * A security sensitive application can check this flag to identify situations in which
+     * a malicious application may have covered up part of its content for the purpose
+     * of misleading the user or hijacking touches.  An appropriate response might be
+     * to drop the suspect touches or to take additional precautions to confirm the user's
+     * actual intent.
+     *
+     * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is
+     * obstructed in areas other than the touched location.
+     */
+    WINDOW_IS_PARTIALLY_OBSCURED = 0x2,
+
+    /**
+     * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that
+     * this event will be immediately followed by a {@link #ACTION_HOVER_EXIT}. It is used to
+     * prevent generating redundant {@link #ACTION_HOVER_ENTER} events.
+     * @hide
+     */
+    HOVER_EXIT_PENDING = 0x4,
+
+    /**
+     * This flag indicates that the event has been generated by a gesture generator. It
+     * provides a hint to the GestureDetector to not apply any touch slop.
+     *
+     * @hide
+     */
+    IS_GENERATED_GESTURE = 0x8,
+
+    /**
+     * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}.
+     * It indicates that the pointer going up was an unintentional user touch. When FLAG_CANCELED
+     * is set, the typical actions that occur in response for a pointer going up (such as click
+     * handlers, end of drawing) should be aborted. This flag is typically set when the user was
+     * accidentally touching the screen, such as by gripping the device, or placing the palm on the
+     * screen.
+     *
+     * @see #ACTION_POINTER_UP
+     * @see #ACTION_CANCEL
+     */
+    CANCELED = IInputConstants.INPUT_EVENT_FLAG_CANCELED,
+
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an {@link #ACTION_DOWN}. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing the
+     * window into focus.
+     * @hide
+     */
+    NO_FOCUS_CHANGE = 0x40,
+
+    /**
+     * This flag indicates that the event has a valid value for AXIS_ORIENTATION.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_ORIENTATION = 0x80,
+
+    /**
+     * This flag indicates that the pointers' AXIS_ORIENTATION can be used to precisely determine
+     * the direction in which the tool is pointing. The value of the orientation axis will be in
+     * the range [-pi, pi], which represents a full circle. This is usually supported by devices
+     * like styluses.
+     *
+     * Conversely, AXIS_ORIENTATION cannot be used to tell which direction the tool is pointing
+     * when this flag is not set. In this case, the axis value will have a range of [-pi/2, pi/2],
+     * which represents half a circle. This is usually the case for devices like touchscreens and
+     * touchpads, for which it is difficult to tell which direction along the major axis of the
+     * touch ellipse the finger is pointing.
+     *
+     * This is a private flag that is not used in Java.
+     * @hide
+     */
+    PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION = 0x100,
+
+    /**
+     * The input event was generated or modified by accessibility service.
+     * Shared by both KeyEvent and MotionEvent flags, so this value should not overlap with either
+     * set of flags, including in input/Input.h and in android/input.h.
+     */
+    IS_ACCESSIBILITY_EVENT = IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+
+    /**
+     * Private flag that indicates when the system has detected that this motion event
+     * may be inconsistent with respect to the sequence of previously delivered motion events,
+     * such as when a pointer move event is sent but the pointer is not down.
+     *
+     * @hide
+     * @see #isTainted
+     * @see #setTainted
+     */
+    TAINTED = IInputConstants.INPUT_EVENT_FLAG_TAINTED,
+
+    /**
+     * Private flag indicating that this event was synthesized by the system and should be delivered
+     * to the accessibility focused view first. When being dispatched such an event is not handled
+     * by predecessors of the accessibility focused view and after the event reaches that view the
+     * flag is cleared and normal event dispatch is performed. This ensures that the platform can
+     * click on any view that has accessibility focus which is semantically equivalent to asking the
+     * view to perform a click accessibility action but more generic as views not implementing click
+     * action correctly can still be activated.
+     *
+     * @hide
+     * @see #isTargetAccessibilityFocus()
+     * @see #setTargetAccessibilityFocus(boolean)
+     */
+    TARGET_ACCESSIBILITY_FOCUS = 0x40000000,
+}
diff --git a/libs/input/android/os/PointerIconType.aidl b/libs/input/android/os/PointerIconType.aidl
new file mode 100644
index 0000000..f244c62
--- /dev/null
+++ b/libs/input/android/os/PointerIconType.aidl
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * Please look at frameworks/base/core/java/android/view/PointerIcon.java for the detailed
+ * explanation of each constant.
+ * @hide
+ */
+@Backing(type="int")
+enum PointerIconType {
+    CUSTOM                  = -1,
+    TYPE_NULL               = 0,
+    NOT_SPECIFIED           = 1,
+    ARROW                   = 1000,
+    CONTEXT_MENU            = 1001,
+    HAND                    = 1002,
+    HELP                    = 1003,
+    WAIT                    = 1004,
+    CELL                    = 1006,
+    CROSSHAIR               = 1007,
+    TEXT                    = 1008,
+    VERTICAL_TEXT           = 1009,
+    ALIAS                   = 1010,
+    COPY                    = 1011,
+    NO_DROP                 = 1012,
+    ALL_SCROLL              = 1013,
+    HORIZONTAL_DOUBLE_ARROW = 1014,
+    VERTICAL_DOUBLE_ARROW   = 1015,
+    TOP_RIGHT_DOUBLE_ARROW  = 1016,
+    TOP_LEFT_DOUBLE_ARROW   = 1017,
+    ZOOM_IN                 = 1018,
+    ZOOM_OUT                = 1019,
+    GRAB                    = 1020,
+    GRABBING                = 1021,
+    HANDWRITING             = 1022,
+
+    SPOT_HOVER              = 2000,
+    SPOT_TOUCH              = 2001,
+    SPOT_ANCHOR             = 2002,
+}
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index bdec5c3..60fb00e 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -16,20 +16,13 @@
 }
 
 flag {
-  name: "enable_pointer_choreographer"
+  name: "remove_input_channel_from_windowstate"
   namespace: "input"
-  description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
-  bug: "293587049"
+  description: "Do not store a copy of input channel inside WindowState."
+  bug: "323450804"
 }
 
 flag {
-  name: "enable_gestures_library_timer_provider"
-  namespace: "input"
-  description: "Set to true to enable timer support for the touchpad Gestures library"
-  bug: "297192727"
- }
-
- flag {
   name: "enable_input_event_tracing"
   namespace: "input"
   description: "Set to true to enable input event tracing, including always-on tracing on non-user builds"
@@ -44,6 +37,13 @@
 }
 
 flag {
+  name: "split_all_touches"
+  namespace: "input"
+  description: "Set FLAG_SPLIT_TOUCHES to true for all windows, regardless of what they specify. This is essentially deprecating this flag by forcefully enabling the split functionality"
+  bug: "239934827"
+}
+
+flag {
   name: "a11y_crash_on_inconsistent_event_stream"
   namespace: "accessibility"
   description: "Brings back fatal logging for inconsistent event streams originating from accessibility."
@@ -87,19 +87,13 @@
 
 flag {
   name: "override_key_behavior_permission_apis"
+  is_exported: true
   namespace: "input"
   description: "enable override key behavior permission APIs"
   bug: "309018874"
 }
 
 flag {
-  name: "remove_pointer_event_tracking_in_wm"
-  namespace: "input"
-  description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
-  bug: "315321016"
-}
-
-flag {
   name: "enable_new_mouse_pointer_ballistics"
   namespace: "input"
   description: "Change the acceleration curves for mouse pointer movements to match the touchpad ones"
@@ -115,6 +109,7 @@
 
 flag {
   name: "input_device_view_behavior_api"
+  is_exported: true
   namespace: "input"
   description: "Controls the API to provide InputDevice view behavior."
   bug: "246946631"
@@ -126,3 +121,89 @@
   description: "Enable fling scrolling to be stopped by putting a finger on the touchpad again"
   bug: "281106755"
 }
+
+flag {
+  name: "enable_prediction_pruning_via_jerk_thresholding"
+  namespace: "input"
+  description: "Enable prediction pruning based on jerk thresholds."
+  bug: "266747654"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "device_associations"
+  namespace: "input"
+  description: "Binds InputDevice name and InputDevice description against display unique id."
+  bug: "324075859"
+}
+
+flag {
+  name: "enable_multi_device_same_window_stream"
+  namespace: "input"
+  description: "Allow multiple input devices to be active in the same window simultaneously"
+  bug: "330752824"
+}
+
+flag {
+  name: "hide_pointer_indicators_for_secure_windows"
+  namespace: "input"
+  description: "Hide touch and pointer indicators if a secure window is present on display"
+  bug: "325252005"
+}
+
+flag {
+  name: "enable_keyboard_classifier"
+  namespace: "input"
+  description: "Keyboard classifier that classifies all keyboards into alphabetic or non-alphabetic"
+  bug: "263559234"
+}
+
+flag {
+  name: "show_pointers_for_partial_screenshare"
+  namespace: "input"
+  description: "Show touch and pointer indicators when mirroring a single task"
+  bug: "310179437"
+}
+
+flag {
+  name: "include_relative_axis_values_for_captured_touchpads"
+  namespace: "input"
+  description: "Include AXIS_RELATIVE_X and AXIS_RELATIVE_Y values when reporting touches from captured touchpads."
+  bug: "330522990"
+}
+
+flag {
+  name: "enable_per_device_input_latency_metrics"
+  namespace: "input"
+  description: "Capture input latency metrics on a per device granular level using histograms."
+  bug: "270049345"
+}
+
+flag {
+  name: "collect_palm_rejection_quality_metrics"
+  namespace: "input"
+  description: "Collect quality metrics on framework palm rejection."
+  bug: "341717757"
+}
+
+flag {
+  name: "enable_touchpad_no_focus_change"
+  namespace: "input"
+  description: "Prevents touchpad gesture changing window focus."
+  bug: "364460018"
+}
+
+flag {
+  name: "enable_input_policy_profile"
+  namespace: "input"
+  description: "Apply input policy profile for input threads."
+  bug: "347122505"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "keyboard_repeat_keys"
+  namespace: "input"
+  description: "Allow user to enable key repeats or configure timeout before key repeat and key repeat delay rates."
+  bug: "336585002"
+}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
index 018d199..63853f7 100644
--- a/libs/input/rust/Android.bp
+++ b/libs/input/rust/Android.bp
@@ -24,6 +24,8 @@
         "liblogger",
         "liblog_rust",
         "inputconstants-rust",
+        "libserde",
+        "libserde_json",
     ],
     whole_static_libs: [
         "libinput_from_rust_to_cpp",
diff --git a/libs/input/rust/data_store.rs b/libs/input/rust/data_store.rs
new file mode 100644
index 0000000..6bdcefd
--- /dev/null
+++ b/libs/input/rust/data_store.rs
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Contains the DataStore, used to store input related data in a persistent way.
+
+use crate::input::KeyboardType;
+use log::{debug, error};
+use serde::{Deserialize, Serialize};
+use std::fs::File;
+use std::io::{Read, Write};
+use std::path::Path;
+use std::sync::{Arc, RwLock};
+
+/// Data store to be used to store information that persistent across device reboots.
+pub struct DataStore {
+    file_reader_writer: Box<dyn FileReaderWriter>,
+    inner: Arc<RwLock<DataStoreInner>>,
+}
+
+#[derive(Default)]
+struct DataStoreInner {
+    is_loaded: bool,
+    data: Data,
+}
+
+#[derive(Default, Serialize, Deserialize)]
+struct Data {
+    // Map storing data for keyboard classification for specific devices.
+    #[serde(default)]
+    keyboard_classifications: Vec<KeyboardClassification>,
+    // NOTE: Important things to consider:
+    // - Add any data that needs to be persisted here in this struct.
+    // - Mark all new fields with "#[serde(default)]" for backward compatibility.
+    // - Also, you can't modify the already added fields.
+    // - Can add new nested fields to existing structs. e.g. Add another field to the struct
+    //   KeyboardClassification and mark it "#[serde(default)]".
+}
+
+#[derive(Default, Serialize, Deserialize)]
+struct KeyboardClassification {
+    descriptor: String,
+    keyboard_type: KeyboardType,
+    is_finalized: bool,
+}
+
+impl DataStore {
+    /// Creates a new instance of Data store
+    pub fn new(file_reader_writer: Box<dyn FileReaderWriter>) -> Self {
+        Self { file_reader_writer, inner: Default::default() }
+    }
+
+    fn load(&mut self) {
+        if self.inner.read().unwrap().is_loaded {
+            return;
+        }
+        self.load_internal();
+    }
+
+    fn load_internal(&mut self) {
+        let s = self.file_reader_writer.read();
+        let data: Data = if !s.is_empty() {
+            let deserialize: Data = match serde_json::from_str(&s) {
+                Ok(deserialize) => deserialize,
+                Err(msg) => {
+                    error!("Unable to deserialize JSON data into struct: {:?} -> {:?}", msg, s);
+                    Default::default()
+                }
+            };
+            deserialize
+        } else {
+            Default::default()
+        };
+
+        let mut inner = self.inner.write().unwrap();
+        inner.data = data;
+        inner.is_loaded = true;
+    }
+
+    fn save(&mut self) {
+        let string_to_save;
+        {
+            let inner = self.inner.read().unwrap();
+            string_to_save = serde_json::to_string(&inner.data).unwrap();
+        }
+        self.file_reader_writer.write(string_to_save);
+    }
+
+    /// Get keyboard type of the device (as stored in the data store)
+    pub fn get_keyboard_type(&mut self, descriptor: &String) -> Option<(KeyboardType, bool)> {
+        self.load();
+        let data = &self.inner.read().unwrap().data;
+        for keyboard_classification in data.keyboard_classifications.iter() {
+            if keyboard_classification.descriptor == *descriptor {
+                return Some((
+                    keyboard_classification.keyboard_type,
+                    keyboard_classification.is_finalized,
+                ));
+            }
+        }
+        None
+    }
+
+    /// Save keyboard type of the device in the data store
+    pub fn set_keyboard_type(
+        &mut self,
+        descriptor: &String,
+        keyboard_type: KeyboardType,
+        is_finalized: bool,
+    ) {
+        {
+            let data = &mut self.inner.write().unwrap().data;
+            data.keyboard_classifications
+                .retain(|classification| classification.descriptor != *descriptor);
+            data.keyboard_classifications.push(KeyboardClassification {
+                descriptor: descriptor.to_string(),
+                keyboard_type,
+                is_finalized,
+            })
+        }
+        self.save();
+    }
+}
+
+pub trait FileReaderWriter {
+    fn read(&self) -> String;
+    fn write(&self, to_write: String);
+}
+
+/// Default file reader writer implementation
+pub struct DefaultFileReaderWriter {
+    filepath: String,
+}
+
+impl DefaultFileReaderWriter {
+    /// Creates a new instance of Default file reader writer that can read and write string to a
+    /// particular file in the filesystem
+    pub fn new(filepath: String) -> Self {
+        Self { filepath }
+    }
+}
+
+impl FileReaderWriter for DefaultFileReaderWriter {
+    fn read(&self) -> String {
+        let path = Path::new(&self.filepath);
+        let mut fs_string = String::new();
+        match File::open(path) {
+            Err(e) => error!("couldn't open {:?}: {}", path, e),
+            Ok(mut file) => match file.read_to_string(&mut fs_string) {
+                Err(e) => error!("Couldn't read from {:?}: {}", path, e),
+                Ok(_) => debug!("Successfully read from file {:?}", path),
+            },
+        };
+        fs_string
+    }
+
+    fn write(&self, to_write: String) {
+        let path = Path::new(&self.filepath);
+        match File::create(path) {
+            Err(e) => error!("couldn't create {:?}: {}", path, e),
+            Ok(mut file) => match file.write_all(to_write.as_bytes()) {
+                Err(e) => error!("Couldn't write to {:?}: {}", path, e),
+                Ok(_) => debug!("Successfully saved to file {:?}", path),
+            },
+        };
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::data_store::{
+        test_file_reader_writer::TestFileReaderWriter, DataStore, FileReaderWriter,
+    };
+    use crate::input::KeyboardType;
+
+    #[test]
+    fn test_backward_compatibility_version_1() {
+        // This test tests JSON string that will be created by the first version of data store
+        // This test SHOULD NOT be modified
+        let test_reader_writer = TestFileReaderWriter::new();
+        test_reader_writer.write(r#"{"keyboard_classifications":[{"descriptor":"descriptor","keyboard_type":{"type":"Alphabetic"},"is_finalized":true}]}"#.to_string());
+
+        let mut data_store = DataStore::new(Box::new(test_reader_writer));
+        let (keyboard_type, is_finalized) =
+            data_store.get_keyboard_type(&"descriptor".to_string()).unwrap();
+        assert_eq!(keyboard_type, KeyboardType::Alphabetic);
+        assert!(is_finalized);
+    }
+}
+
+#[cfg(test)]
+pub mod test_file_reader_writer {
+
+    use crate::data_store::FileReaderWriter;
+    use std::sync::{Arc, RwLock};
+
+    #[derive(Default)]
+    struct TestFileReaderWriterInner {
+        fs_string: String,
+    }
+
+    #[derive(Default, Clone)]
+    pub struct TestFileReaderWriter(Arc<RwLock<TestFileReaderWriterInner>>);
+
+    impl TestFileReaderWriter {
+        pub fn new() -> Self {
+            Default::default()
+        }
+    }
+
+    impl FileReaderWriter for TestFileReaderWriter {
+        fn read(&self) -> String {
+            self.0.read().unwrap().fs_string.clone()
+        }
+
+        fn write(&self, fs_string: String) {
+            self.0.write().unwrap().fs_string = fs_string;
+        }
+    }
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
index 705c959..90f509d 100644
--- a/libs/input/rust/input.rs
+++ b/libs/input/rust/input.rs
@@ -16,13 +16,28 @@
 
 //! Common definitions of the Android Input Framework in rust.
 
+use crate::ffi::RustInputDeviceIdentifier;
 use bitflags::bitflags;
+use inputconstants::aidl::android::os::IInputConstants;
+use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+use serde::{Deserialize, Serialize};
 use std::fmt;
 
 /// The InputDevice ID.
 #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
 pub struct DeviceId(pub i32);
 
+/// The InputDevice equivalent for Rust inputflinger
+#[derive(Debug)]
+pub struct InputDevice {
+    /// InputDevice ID
+    pub device_id: DeviceId,
+    /// InputDevice unique identifier
+    pub identifier: RustInputDeviceIdentifier,
+    /// InputDevice classes (equivalent to EventHub InputDeviceClass)
+    pub classes: DeviceClass,
+}
+
 #[repr(u32)]
 pub enum SourceClass {
     None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
@@ -180,20 +195,34 @@
 
 bitflags! {
     /// MotionEvent flags.
+    /// The source of truth for the flag definitions are the MotionEventFlag AIDL enum.
+    /// The flag values are redefined here as a bitflags API.
     #[derive(Debug)]
     pub struct MotionFlags: u32 {
-        /// FLAG_CANCELED
-        const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
         /// FLAG_WINDOW_IS_OBSCURED
-        const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+        const WINDOW_IS_OBSCURED = MotionEventFlag::WINDOW_IS_OBSCURED.0 as u32;
         /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-        const WINDOW_IS_PARTIALLY_OBSCURED =
-                input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
-        /// FLAG_IS_ACCESSIBILITY_EVENT
-        const IS_ACCESSIBILITY_EVENT =
-                input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+        const WINDOW_IS_PARTIALLY_OBSCURED = MotionEventFlag::WINDOW_IS_PARTIALLY_OBSCURED.0 as u32;
+        /// FLAG_HOVER_EXIT_PENDING
+        const HOVER_EXIT_PENDING = MotionEventFlag::HOVER_EXIT_PENDING.0 as u32;
+        /// FLAG_IS_GENERATED_GESTURE
+        const IS_GENERATED_GESTURE = MotionEventFlag::IS_GENERATED_GESTURE.0 as u32;
+        /// FLAG_CANCELED
+        const CANCELED = MotionEventFlag::CANCELED.0 as u32;
         /// FLAG_NO_FOCUS_CHANGE
-        const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+        const NO_FOCUS_CHANGE = MotionEventFlag::NO_FOCUS_CHANGE.0 as u32;
+        /// PRIVATE_FLAG_SUPPORTS_ORIENTATION
+        const PRIVATE_FLAG_SUPPORTS_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_ORIENTATION.0 as u32;
+        /// PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION
+        const PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION =
+                MotionEventFlag::PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION.0 as u32;
+        /// FLAG_IS_ACCESSIBILITY_EVENT
+        const IS_ACCESSIBILITY_EVENT = MotionEventFlag::IS_ACCESSIBILITY_EVENT.0 as u32;
+        /// FLAG_TAINTED
+        const TAINTED = MotionEventFlag::TAINTED.0 as u32;
+        /// FLAG_TARGET_ACCESSIBILITY_FOCUS
+        const TARGET_ACCESSIBILITY_FOCUS = MotionEventFlag::TARGET_ACCESSIBILITY_FOCUS.0 as u32;
     }
 }
 
@@ -205,13 +234,130 @@
     }
 }
 
+bitflags! {
+    /// Device class of the input device. These are duplicated from Eventhub.h
+    /// We need to make sure the two version remain in sync when adding new classes.
+    #[derive(Debug, PartialEq)]
+    pub struct DeviceClass: u32 {
+        /// The input device is a keyboard or has buttons
+        const Keyboard = IInputConstants::DEVICE_CLASS_KEYBOARD as u32;
+        /// The input device is an alpha-numeric keyboard (not just a dial pad)
+        const AlphabeticKey = IInputConstants::DEVICE_CLASS_ALPHAKEY as u32;
+        /// The input device is a touchscreen or a touchpad (either single-touch or multi-touch)
+        const Touch = IInputConstants::DEVICE_CLASS_TOUCH as u32;
+        /// The input device is a cursor device such as a trackball or mouse.
+        const Cursor = IInputConstants::DEVICE_CLASS_CURSOR as u32;
+        /// The input device is a multi-touch touchscreen or touchpad.
+        const MultiTouch = IInputConstants::DEVICE_CLASS_TOUCH_MT as u32;
+        /// The input device is a directional pad (implies keyboard, has DPAD keys).
+        const Dpad = IInputConstants::DEVICE_CLASS_DPAD as u32;
+        /// The input device is a gamepad (implies keyboard, has BUTTON keys).
+        const Gamepad = IInputConstants::DEVICE_CLASS_GAMEPAD as u32;
+        /// The input device has switches.
+        const Switch = IInputConstants::DEVICE_CLASS_SWITCH as u32;
+        /// The input device is a joystick (implies gamepad, has joystick absolute axes).
+        const Joystick = IInputConstants::DEVICE_CLASS_JOYSTICK as u32;
+        /// The input device has a vibrator (supports FF_RUMBLE).
+        const Vibrator = IInputConstants::DEVICE_CLASS_VIBRATOR as u32;
+        /// The input device has a microphone.
+        const Mic = IInputConstants::DEVICE_CLASS_MIC as u32;
+        /// The input device is an external stylus (has data we want to fuse with touch data).
+        const ExternalStylus = IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS as u32;
+        /// The input device has a rotary encoder
+        const RotaryEncoder = IInputConstants::DEVICE_CLASS_ROTARY_ENCODER as u32;
+        /// The input device has a sensor like accelerometer, gyro, etc
+        const Sensor = IInputConstants::DEVICE_CLASS_SENSOR as u32;
+        /// The input device has a battery
+        const Battery = IInputConstants::DEVICE_CLASS_BATTERY as u32;
+        /// The input device has sysfs controllable lights
+        const Light = IInputConstants::DEVICE_CLASS_LIGHT as u32;
+        /// The input device is a touchpad, requiring an on-screen cursor.
+        const Touchpad = IInputConstants::DEVICE_CLASS_TOUCHPAD as u32;
+        /// The input device is virtual (not a real device, not part of UI configuration).
+        const Virtual = IInputConstants::DEVICE_CLASS_VIRTUAL as u32;
+        /// The input device is external (not built-in).
+        const External = IInputConstants::DEVICE_CLASS_EXTERNAL as u32;
+    }
+}
+
+bitflags! {
+    /// Modifier state flags
+    #[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
+    pub struct ModifierState: u32 {
+        /// No meta keys are pressed
+        const None = input_bindgen::AMETA_NONE;
+        /// This mask is used to check whether one of the ALT meta keys is pressed
+        const AltOn = input_bindgen::AMETA_ALT_ON;
+        /// This mask is used to check whether the left ALT meta key is pressed
+        const AltLeftOn = input_bindgen::AMETA_ALT_LEFT_ON;
+        /// This mask is used to check whether the right ALT meta key is pressed
+        const AltRightOn = input_bindgen::AMETA_ALT_RIGHT_ON;
+        /// This mask is used to check whether one of the SHIFT meta keys is pressed
+        const ShiftOn = input_bindgen::AMETA_SHIFT_ON;
+        /// This mask is used to check whether the left SHIFT meta key is pressed
+        const ShiftLeftOn = input_bindgen::AMETA_SHIFT_LEFT_ON;
+        /// This mask is used to check whether the right SHIFT meta key is pressed
+        const ShiftRightOn = input_bindgen::AMETA_SHIFT_RIGHT_ON;
+        /// This mask is used to check whether the SYM meta key is pressed
+        const SymOn = input_bindgen::AMETA_SYM_ON;
+        /// This mask is used to check whether the FUNCTION meta key is pressed
+        const FunctionOn = input_bindgen::AMETA_FUNCTION_ON;
+        /// This mask is used to check whether one of the CTRL meta keys is pressed
+        const CtrlOn = input_bindgen::AMETA_CTRL_ON;
+        /// This mask is used to check whether the left CTRL meta key is pressed
+        const CtrlLeftOn = input_bindgen::AMETA_CTRL_LEFT_ON;
+        /// This mask is used to check whether the right CTRL meta key is pressed
+        const CtrlRightOn = input_bindgen::AMETA_CTRL_RIGHT_ON;
+        /// This mask is used to check whether one of the META meta keys is pressed
+        const MetaOn = input_bindgen::AMETA_META_ON;
+        /// This mask is used to check whether the left META meta key is pressed
+        const MetaLeftOn = input_bindgen::AMETA_META_LEFT_ON;
+        /// This mask is used to check whether the right META meta key is pressed
+        const MetaRightOn = input_bindgen::AMETA_META_RIGHT_ON;
+        /// This mask is used to check whether the CAPS LOCK meta key is on
+        const CapsLockOn = input_bindgen::AMETA_CAPS_LOCK_ON;
+        /// This mask is used to check whether the NUM LOCK meta key is on
+        const NumLockOn = input_bindgen::AMETA_NUM_LOCK_ON;
+        /// This mask is used to check whether the SCROLL LOCK meta key is on
+        const ScrollLockOn = input_bindgen::AMETA_SCROLL_LOCK_ON;
+    }
+}
+
+/// A rust enum representation of a Keyboard type.
+#[repr(u32)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
+#[serde(tag = "type")]
+pub enum KeyboardType {
+    /// KEYBOARD_TYPE_NONE
+    #[default]
+    None = input_bindgen::AINPUT_KEYBOARD_TYPE_NONE,
+    /// KEYBOARD_TYPE_NON_ALPHABETIC
+    NonAlphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+    /// KEYBOARD_TYPE_ALPHABETIC
+    Alphabetic = input_bindgen::AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+}
+
 #[cfg(test)]
 mod tests {
     use crate::input::SourceClass;
+    use crate::MotionFlags;
     use crate::Source;
+    use inputconstants::aidl::android::os::MotionEventFlag::MotionEventFlag;
+
     #[test]
     fn convert_source_class_pointer() {
         let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
         assert!(source.is_from_class(SourceClass::Pointer));
     }
+
+    /// Ensure all MotionEventFlag constants are re-defined in rust.
+    #[test]
+    fn all_motion_event_flags_defined() {
+        for flag in MotionEventFlag::enum_values() {
+            assert!(
+                MotionFlags::from_bits(flag.0 as u32).is_some(),
+                "MotionEventFlag value {flag:?} is not redefined as a rust MotionFlags"
+            );
+        }
+    }
 }
diff --git a/libs/input/rust/keyboard_classification_config.rs b/libs/input/rust/keyboard_classification_config.rs
new file mode 100644
index 0000000..ab74efb
--- /dev/null
+++ b/libs/input/rust/keyboard_classification_config.rs
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2024 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.
+ */
+
+use crate::input::KeyboardType;
+
+// TODO(b/263559234): Categorize some of these to KeyboardType::None based on ability to produce
+//  key events at all. (Requires setup allowing InputDevice to dynamically add/remove
+//  KeyboardInputMapper based on blocklist and KeyEvents in case a KeyboardType::None device ends
+//  up producing a key event)
+pub static CLASSIFIED_DEVICES: &[(
+    /* vendorId */ u16,
+    /* productId */ u16,
+    KeyboardType,
+    /* is_finalized */ bool,
+)] = &[
+    // HP X4000 Wireless Mouse
+    (0x03f0, 0xa407, KeyboardType::NonAlphabetic, true),
+    // Microsoft Wireless Mobile Mouse 6000
+    (0x045e, 0x0745, KeyboardType::NonAlphabetic, true),
+    // Microsoft Surface Precision Mouse
+    (0x045e, 0x0821, KeyboardType::NonAlphabetic, true),
+    // Microsoft Pro IntelliMouse
+    (0x045e, 0x082a, KeyboardType::NonAlphabetic, true),
+    // Microsoft Bluetooth Mouse
+    (0x045e, 0x082f, KeyboardType::NonAlphabetic, true),
+    // Xbox One Elite Series 2 gamepad
+    (0x045e, 0x0b05, KeyboardType::NonAlphabetic, true),
+    // Logitech T400
+    (0x046d, 0x4026, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Unifying)
+    (0x046d, 0x405e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Unifying)
+    (0x046d, 0x4069, KeyboardType::NonAlphabetic, true),
+    // Logitech M585 (Unifying)
+    (0x046d, 0x406b, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Unifying)
+    (0x046d, 0x4072, KeyboardType::NonAlphabetic, true),
+    // Logitech Pebble M350
+    (0x046d, 0x4080, KeyboardType::NonAlphabetic, true),
+    // Logitech T630 Ultrathin
+    (0x046d, 0xb00d, KeyboardType::NonAlphabetic, true),
+    // Logitech M558
+    (0x046d, 0xb011, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb012, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb013, KeyboardType::NonAlphabetic, true),
+    // Logitech M720 Triathlon (Bluetooth)
+    (0x046d, 0xb015, KeyboardType::NonAlphabetic, true),
+    // Logitech M535
+    (0x046d, 0xb016, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master / Anywhere 2 (Bluetooth)
+    (0x046d, 0xb017, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 2S (Bluetooth)
+    (0x046d, 0xb019, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2S (Bluetooth)
+    (0x046d, 0xb01a, KeyboardType::NonAlphabetic, true),
+    // Logitech M585/M590 (Bluetooth)
+    (0x046d, 0xb01b, KeyboardType::NonAlphabetic, true),
+    // Logitech G603 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb01c, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master (Bluetooth)
+    (0x046d, 0xb01e, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Anywhere 2 (Bluetooth)
+    (0x046d, 0xb01f, KeyboardType::NonAlphabetic, true),
+    // Logitech MX Master 3 (Bluetooth)
+    (0x046d, 0xb023, KeyboardType::NonAlphabetic, true),
+    // Logitech G604 Lightspeed Gaming Mouse (Bluetooth)
+    (0x046d, 0xb024, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (Bluetooth)
+    (0x046d, 0xb503, KeyboardType::NonAlphabetic, true),
+    // Logitech R500 (Bluetooth)
+    (0x046d, 0xb505, KeyboardType::NonAlphabetic, true),
+    // Logitech M500s
+    (0x046d, 0xc093, KeyboardType::NonAlphabetic, true),
+    // Logitech Spotlight Presentation Remote (USB dongle)
+    (0x046d, 0xc53e, KeyboardType::NonAlphabetic, true),
+    // Elecom Enelo IR LED Mouse 350
+    (0x056e, 0x0134, KeyboardType::NonAlphabetic, true),
+    // Elecom EPRIM Blue LED 5 button mouse 228
+    (0x056e, 0x0141, KeyboardType::NonAlphabetic, true),
+    // Elecom Blue LED Mouse 203
+    (0x056e, 0x0159, KeyboardType::NonAlphabetic, true),
+    // Zebra LS2208 barcode scanner
+    (0x05e0, 0x1200, KeyboardType::NonAlphabetic, true),
+    // RDing FootSwitch1F1
+    (0x0c45, 0x7403, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Sensei RAW Frost Blue
+    (0x1038, 0x1369, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wired
+    (0x1038, 0x1824, KeyboardType::NonAlphabetic, true),
+    // SteelSeries Rival 3 Wireless (USB dongle)
+    (0x1038, 0x1830, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey
+    (0x1050, 0x0010, KeyboardType::NonAlphabetic, true),
+    // Yubico.com Yubikey 4 OTP+U2F+CCID
+    (0x1050, 0x0407, KeyboardType::NonAlphabetic, true),
+    // Lenovo USB-C Wired Compact Mouse
+    (0x17ef, 0x6123, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (USB dongle)
+    (0x1b1c, 0x1b94, KeyboardType::NonAlphabetic, true),
+    // Corsair Katar Pro Wireless (Bluetooth)
+    (0x1bae, 0x1b1c, KeyboardType::NonAlphabetic, true),
+    // Kensington Pro Fit Full-size
+    (0x1bcf, 0x08a0, KeyboardType::NonAlphabetic, true),
+    // Huion HS64
+    (0x256c, 0x006d, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Star G640
+    (0x28bd, 0x0914, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Artist 12 Pro
+    (0x28bd, 0x091f, KeyboardType::NonAlphabetic, true),
+    // XP-Pen Deco mini7W
+    (0x28bd, 0x0928, KeyboardType::NonAlphabetic, true),
+];
diff --git a/libs/input/rust/keyboard_classifier.rs b/libs/input/rust/keyboard_classifier.rs
new file mode 100644
index 0000000..1b89a5c
--- /dev/null
+++ b/libs/input/rust/keyboard_classifier.rs
@@ -0,0 +1,428 @@
+/*
+ * Copyright 2024 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.
+ */
+
+//! Contains the KeyboardClassifier, that tries to identify whether an Input device is an
+//! alphabetic or non-alphabetic keyboard. It also tracks the KeyEvents produced by the device
+//! in order to verify/change the inferred keyboard type.
+//!
+//! Initial classification:
+//! - If DeviceClass includes Dpad, Touch, Cursor, MultiTouch, ExternalStylus, Touchpad, Dpad,
+//!   Gamepad, Switch, Joystick, RotaryEncoder => KeyboardType::NonAlphabetic
+//! - Otherwise if DeviceClass has Keyboard and not AlphabeticKey => KeyboardType::NonAlphabetic
+//! - Otherwise if DeviceClass has both Keyboard and AlphabeticKey => KeyboardType::Alphabetic
+//!
+//! On process keys:
+//! - If KeyboardType::NonAlphabetic and we receive alphabetic key event, then change type to
+//!   KeyboardType::Alphabetic. Once changed, no further changes. (i.e. verified = true)
+//! - TODO(b/263559234): If KeyboardType::Alphabetic and we don't receive any alphabetic key event
+//!    across multiple device connections in a time period, then change type to
+//!    KeyboardType::NonAlphabetic. Once changed, it can still change back to Alphabetic
+//!    (i.e. verified = false).
+
+use crate::data_store::DataStore;
+use crate::input::{DeviceId, InputDevice, KeyboardType};
+use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
+use crate::{DeviceClass, ModifierState};
+use std::collections::HashMap;
+
+/// The KeyboardClassifier is used to classify a keyboard device into non-keyboard, alphabetic
+/// keyboard or non-alphabetic keyboard
+pub struct KeyboardClassifier {
+    device_map: HashMap<DeviceId, KeyboardInfo>,
+    data_store: DataStore,
+}
+
+struct KeyboardInfo {
+    device: InputDevice,
+    keyboard_type: KeyboardType,
+    is_finalized: bool,
+}
+
+impl KeyboardClassifier {
+    /// Create a new KeyboardClassifier
+    pub fn new(data_store: DataStore) -> Self {
+        Self { device_map: HashMap::new(), data_store }
+    }
+
+    /// Adds keyboard to KeyboardClassifier
+    pub fn notify_keyboard_changed(&mut self, device: InputDevice) {
+        let (keyboard_type, is_finalized) = self.classify_keyboard(&device);
+        self.device_map
+            .insert(device.device_id, KeyboardInfo { device, keyboard_type, is_finalized });
+    }
+
+    /// Get keyboard type for a tracked keyboard in KeyboardClassifier
+    pub fn get_keyboard_type(&self, device_id: DeviceId) -> KeyboardType {
+        if let Some(keyboard) = self.device_map.get(&device_id) {
+            keyboard.keyboard_type
+        } else {
+            KeyboardType::None
+        }
+    }
+
+    /// Tells if keyboard type classification is finalized. Once finalized the classification can't
+    /// change until device is reconnected again.
+    ///
+    /// Finalized devices are either "alphabetic" keyboards or keyboards in blocklist or
+    /// allowlist that are explicitly categorized and won't change with future key events
+    pub fn is_finalized(&self, device_id: DeviceId) -> bool {
+        if let Some(keyboard) = self.device_map.get(&device_id) {
+            keyboard.is_finalized
+        } else {
+            false
+        }
+    }
+
+    /// Process a key event and change keyboard type if required.
+    /// - If any key event occurs, the keyboard type will change from None to NonAlphabetic
+    /// - If an alphabetic key occurs, the keyboard type will change to Alphabetic
+    pub fn process_key(
+        &mut self,
+        device_id: DeviceId,
+        evdev_code: i32,
+        modifier_state: ModifierState,
+    ) {
+        if let Some(keyboard) = self.device_map.get_mut(&device_id) {
+            // Ignore all key events with modifier state since they can be macro shortcuts used by
+            // some non-keyboard peripherals like TV remotes, game controllers, etc.
+            if modifier_state.bits() != 0 {
+                return;
+            }
+            if Self::is_alphabetic_key(&evdev_code) {
+                keyboard.keyboard_type = KeyboardType::Alphabetic;
+                keyboard.is_finalized = true;
+                self.data_store.set_keyboard_type(
+                    &keyboard.device.identifier.descriptor,
+                    keyboard.keyboard_type,
+                    keyboard.is_finalized,
+                );
+            }
+        }
+    }
+
+    fn classify_keyboard(&mut self, device: &InputDevice) -> (KeyboardType, bool) {
+        // This should never happen but having keyboard device class is necessary to be classified
+        // as any type of keyboard.
+        if !device.classes.contains(DeviceClass::Keyboard) {
+            return (KeyboardType::None, true);
+        }
+        // Normal classification for internal and virtual keyboards
+        if !device.classes.contains(DeviceClass::External)
+            || device.classes.contains(DeviceClass::Virtual)
+        {
+            return if device.classes.contains(DeviceClass::AlphabeticKey) {
+                (KeyboardType::Alphabetic, true)
+            } else {
+                (KeyboardType::NonAlphabetic, true)
+            };
+        }
+
+        // Check in data store
+        if let Some((keyboard_type, is_finalized)) =
+            self.data_store.get_keyboard_type(&device.identifier.descriptor)
+        {
+            return (keyboard_type, is_finalized);
+        }
+
+        // Check in known device list for classification
+        for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
+            if device.identifier.vendor == *vendor && device.identifier.product == *product {
+                return (*keyboard_type, *is_finalized);
+            }
+        }
+
+        // Any composite device with multiple device classes should be categorized as non-alphabetic
+        // keyboard initially
+        if device.classes.contains(DeviceClass::Touch)
+            || device.classes.contains(DeviceClass::Cursor)
+            || device.classes.contains(DeviceClass::MultiTouch)
+            || device.classes.contains(DeviceClass::ExternalStylus)
+            || device.classes.contains(DeviceClass::Touchpad)
+            || device.classes.contains(DeviceClass::Dpad)
+            || device.classes.contains(DeviceClass::Gamepad)
+            || device.classes.contains(DeviceClass::Switch)
+            || device.classes.contains(DeviceClass::Joystick)
+            || device.classes.contains(DeviceClass::RotaryEncoder)
+        {
+            // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
+            // kernel, we no longer need to process key events to verify.
+            return (
+                KeyboardType::NonAlphabetic,
+                !device.classes.contains(DeviceClass::AlphabeticKey),
+            );
+        }
+        // Only devices with "Keyboard" and "AlphabeticKey" should be classified as full keyboard
+        if device.classes.contains(DeviceClass::AlphabeticKey) {
+            (KeyboardType::Alphabetic, true)
+        } else {
+            // If categorized as NonAlphabetic and no device class AlphabeticKey reported by the
+            // kernel, we no longer need to process key events to verify.
+            (KeyboardType::NonAlphabetic, true)
+        }
+    }
+
+    fn is_alphabetic_key(evdev_code: &i32) -> bool {
+        // Keyboard alphabetic row 1 (Q W E R T Y U I O P [ ])
+        (16..=27).contains(evdev_code)
+            // Keyboard alphabetic row 2 (A S D F G H J K L ; ' `)
+            || (30..=41).contains(evdev_code)
+            // Keyboard alphabetic row 3 (\ Z X C V B N M , . /)
+            || (43..=53).contains(evdev_code)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::data_store::{test_file_reader_writer::TestFileReaderWriter, DataStore};
+    use crate::input::{DeviceId, InputDevice, KeyboardType};
+    use crate::keyboard_classification_config::CLASSIFIED_DEVICES;
+    use crate::keyboard_classifier::KeyboardClassifier;
+    use crate::{DeviceClass, ModifierState, RustInputDeviceIdentifier};
+
+    static DEVICE_ID: DeviceId = DeviceId(1);
+    static SECOND_DEVICE_ID: DeviceId = DeviceId(2);
+    static KEY_A: i32 = 30;
+    static KEY_1: i32 = 2;
+
+    #[test]
+    fn classify_external_alphabetic_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_external_non_alphabetic_keyboard() {
+        let mut classifier = create_classifier();
+        classifier
+            .notify_keyboard_changed(create_device(DeviceClass::Keyboard | DeviceClass::External));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_mouse_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Cursor
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_touchpad_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Touchpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_stylus_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::ExternalStylus
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_dpad_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_joystick_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Joystick
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_gamepad_pretending_as_keyboard() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Gamepad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn reclassify_keyboard_on_alphabetic_key_event() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        // on alphabetic key event
+        classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::Alphabetic);
+        assert!(classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn dont_reclassify_keyboard_on_non_alphabetic_key_event() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        // on number key event
+        classifier.process_key(DEVICE_ID, KEY_1, ModifierState::None);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn dont_reclassify_keyboard_on_alphabetic_key_event_with_modifiers() {
+        let mut classifier = create_classifier();
+        classifier.notify_keyboard_changed(create_device(
+            DeviceClass::Keyboard
+                | DeviceClass::Dpad
+                | DeviceClass::AlphabeticKey
+                | DeviceClass::External,
+        ));
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+
+        classifier.process_key(DEVICE_ID, KEY_A, ModifierState::CtrlOn);
+        assert_eq!(classifier.get_keyboard_type(DEVICE_ID), KeyboardType::NonAlphabetic);
+        assert!(!classifier.is_finalized(DEVICE_ID));
+    }
+
+    #[test]
+    fn classify_known_devices() {
+        let mut classifier = create_classifier();
+        for (vendor, product, keyboard_type, is_finalized) in CLASSIFIED_DEVICES.iter() {
+            classifier
+                .notify_keyboard_changed(create_device_with_vendor_product_ids(*vendor, *product));
+            assert_eq!(classifier.get_keyboard_type(DEVICE_ID), *keyboard_type);
+            assert_eq!(classifier.is_finalized(DEVICE_ID), *is_finalized);
+        }
+    }
+
+    #[test]
+    fn classify_previously_reclassified_devices() {
+        let test_reader_writer = TestFileReaderWriter::new();
+        {
+            let mut classifier =
+                KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
+            let device = create_device(
+                DeviceClass::Keyboard
+                    | DeviceClass::Dpad
+                    | DeviceClass::AlphabeticKey
+                    | DeviceClass::External,
+            );
+            classifier.notify_keyboard_changed(device);
+            classifier.process_key(DEVICE_ID, KEY_A, ModifierState::None);
+        }
+
+        // Re-create classifier and data store to mimic a reboot (but use the same file system
+        // reader writer)
+        {
+            let mut classifier =
+                KeyboardClassifier::new(DataStore::new(Box::new(test_reader_writer.clone())));
+            let device = InputDevice {
+                device_id: SECOND_DEVICE_ID,
+                identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
+                classes: DeviceClass::Keyboard
+                    | DeviceClass::Dpad
+                    | DeviceClass::AlphabeticKey
+                    | DeviceClass::External,
+            };
+            classifier.notify_keyboard_changed(device);
+            assert_eq!(classifier.get_keyboard_type(SECOND_DEVICE_ID), KeyboardType::Alphabetic);
+            assert!(classifier.is_finalized(SECOND_DEVICE_ID));
+        }
+    }
+
+    fn create_classifier() -> KeyboardClassifier {
+        KeyboardClassifier::new(DataStore::new(Box::new(TestFileReaderWriter::new())))
+    }
+
+    fn create_identifier(vendor: u16, product: u16) -> RustInputDeviceIdentifier {
+        RustInputDeviceIdentifier {
+            name: "test_device".to_string(),
+            location: "location".to_string(),
+            unique_id: "unique_id".to_string(),
+            bus: 123,
+            vendor,
+            product,
+            version: 567,
+            descriptor: "descriptor".to_string(),
+        }
+    }
+
+    fn create_device(classes: DeviceClass) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: create_identifier(/* vendor= */ 234, /* product= */ 345),
+            classes,
+        }
+    }
+
+    fn create_device_with_vendor_product_ids(vendor: u16, product: u16) -> InputDevice {
+        InputDevice {
+            device_id: DEVICE_ID,
+            identifier: create_identifier(vendor, product),
+            classes: DeviceClass::Keyboard | DeviceClass::AlphabeticKey | DeviceClass::External,
+        }
+    }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
index 01d9599..4f4ea85 100644
--- a/libs/input/rust/lib.rs
+++ b/libs/input/rust/lib.rs
@@ -16,13 +16,22 @@
 
 //! The rust component of libinput.
 
+mod data_store;
 mod input;
 mod input_verifier;
+mod keyboard_classification_config;
+mod keyboard_classifier;
 
-pub use input::{DeviceId, MotionAction, MotionFlags, Source};
+pub use data_store::{DataStore, DefaultFileReaderWriter};
+pub use input::{
+    DeviceClass, DeviceId, InputDevice, KeyboardType, ModifierState, MotionAction, MotionFlags,
+    Source,
+};
 pub use input_verifier::InputVerifier;
+pub use keyboard_classifier::KeyboardClassifier;
 
 #[cxx::bridge(namespace = "android::input")]
+#[allow(clippy::needless_maybe_sized)]
 #[allow(unsafe_op_in_unsafe_fn)]
 mod ffi {
     #[namespace = "android"]
@@ -47,7 +56,8 @@
         /// }
         /// ```
         type InputVerifier;
-        fn create(name: String) -> Box<InputVerifier>;
+        #[cxx_name = create]
+        fn create_input_verifier(name: String) -> Box<InputVerifier>;
         fn process_movement(
             verifier: &mut InputVerifier,
             device_id: i32,
@@ -59,15 +69,53 @@
         fn reset_device(verifier: &mut InputVerifier, device_id: i32);
     }
 
+    #[namespace = "android::input::keyboardClassifier"]
+    extern "Rust" {
+        /// Used to classify a keyboard into alphabetic and non-alphabetic
+        type KeyboardClassifier;
+        #[cxx_name = create]
+        fn create_keyboard_classifier() -> Box<KeyboardClassifier>;
+        #[cxx_name = notifyKeyboardChanged]
+        fn notify_keyboard_changed(
+            classifier: &mut KeyboardClassifier,
+            device_id: i32,
+            identifier: RustInputDeviceIdentifier,
+            device_classes: u32,
+        );
+        #[cxx_name = getKeyboardType]
+        fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32;
+        #[cxx_name = isFinalized]
+        fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool;
+        #[cxx_name = processKey]
+        fn process_key(
+            classifier: &mut KeyboardClassifier,
+            device_id: i32,
+            evdev_code: i32,
+            modifier_state: u32,
+        );
+    }
+
     #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
     pub struct RustPointerProperties {
         pub id: i32,
     }
+
+    #[derive(Debug)]
+    pub struct RustInputDeviceIdentifier {
+        pub name: String,
+        pub location: String,
+        pub unique_id: String,
+        pub bus: u16,
+        pub vendor: u16,
+        pub product: u16,
+        pub version: u16,
+        pub descriptor: String,
+    }
 }
 
-use crate::ffi::RustPointerProperties;
+use crate::ffi::{RustInputDeviceIdentifier, RustPointerProperties};
 
-fn create(name: String) -> Box<InputVerifier> {
+fn create_input_verifier(name: String) -> Box<InputVerifier> {
     Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
 }
 
@@ -79,12 +127,20 @@
     pointer_properties: &[RustPointerProperties],
     flags: u32,
 ) -> String {
+    let motion_flags = MotionFlags::from_bits(flags);
+    if motion_flags.is_none() {
+        panic!(
+            "The conversion of flags 0x{:08x} failed, please check if some flags have not been \
+            added to MotionFlags.",
+            flags
+        );
+    }
     let result = verifier.process_movement(
         DeviceId(device_id),
         Source::from_bits(source).unwrap(),
         action,
         pointer_properties,
-        MotionFlags::from_bits(flags).unwrap(),
+        motion_flags.unwrap(),
     );
     match result {
         Ok(()) => "".to_string(),
@@ -95,3 +151,60 @@
 fn reset_device(verifier: &mut InputVerifier, device_id: i32) {
     verifier.reset_device(DeviceId(device_id));
 }
+
+fn create_keyboard_classifier() -> Box<KeyboardClassifier> {
+    // Future design: Make this data store singleton by passing it to C++ side and making it global
+    // and pass by reference to components that need to store persistent data.
+    //
+    // Currently only used by rust keyboard classifier so keeping it here.
+    let data_store = DataStore::new(Box::new(DefaultFileReaderWriter::new(
+        "/data/system/inputflinger-data.json".to_string(),
+    )));
+    Box::new(KeyboardClassifier::new(data_store))
+}
+
+fn notify_keyboard_changed(
+    classifier: &mut KeyboardClassifier,
+    device_id: i32,
+    identifier: RustInputDeviceIdentifier,
+    device_classes: u32,
+) {
+    let classes = DeviceClass::from_bits(device_classes);
+    if classes.is_none() {
+        panic!(
+            "The conversion of device class 0x{:08x} failed, please check if some device classes
+             have not been added to DeviceClass.",
+            device_classes
+        );
+    }
+    classifier.notify_keyboard_changed(InputDevice {
+        device_id: DeviceId(device_id),
+        identifier,
+        classes: classes.unwrap(),
+    });
+}
+
+fn get_keyboard_type(classifier: &mut KeyboardClassifier, device_id: i32) -> u32 {
+    classifier.get_keyboard_type(DeviceId(device_id)) as u32
+}
+
+fn is_finalized(classifier: &mut KeyboardClassifier, device_id: i32) -> bool {
+    classifier.is_finalized(DeviceId(device_id))
+}
+
+fn process_key(
+    classifier: &mut KeyboardClassifier,
+    device_id: i32,
+    evdev_code: i32,
+    meta_state: u32,
+) {
+    let modifier_state = ModifierState::from_bits(meta_state);
+    if modifier_state.is_none() {
+        panic!(
+            "The conversion of meta state 0x{:08x} failed, please check if some meta state
+             have not been added to ModifierState.",
+            meta_state
+        );
+    }
+    classifier.process_key(DeviceId(device_id), evdev_code, modifier_state.unwrap());
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 93af4c2..e04236b 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -16,13 +16,17 @@
         "BlockingQueue_test.cpp",
         "IdGenerator_test.cpp",
         "InputChannel_test.cpp",
+        "InputConsumer_test.cpp",
         "InputDevice_test.cpp",
         "InputEvent_test.cpp",
         "InputPublisherAndConsumer_test.cpp",
+        "InputPublisherAndConsumerNoResampling_test.cpp",
         "InputVerifier_test.cpp",
         "MotionPredictor_test.cpp",
         "MotionPredictorMetricsManager_test.cpp",
+        "Resampler_test.cpp",
         "RingBuffer_test.cpp",
+        "TestInputChannel.cpp",
         "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
         "TouchVideoFrame_test.cpp",
@@ -35,6 +39,7 @@
         "tensorflow_headers",
     ],
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libgui_window_info_static",
         "libinput",
@@ -58,11 +63,13 @@
         },
     },
     shared_libs: [
+        "libaconfig_storage_read_api_cc",
         "libbase",
         "libbinder",
         "libcutils",
         "liblog",
         "libPlatformProperties",
+        "libstatslog",
         "libtinyxml2",
         "libutils",
         "server_configurable_flags",
@@ -76,20 +83,19 @@
     },
     test_suites: ["device-tests"],
     target: {
+        android: {
+            static_libs: [
+                "libstatslog_libinput",
+                "libstatssocket_lazy",
+            ],
+        },
         host: {
             sanitize: {
                 address: true,
             },
         },
-        android: {
-            static_libs: [
-                // Stats logging library and its dependencies.
-                "libstatslog_libinput",
-                "libstatsbootstrap",
-                "android.os.statsbootstrap_aidl-cpp",
-            ],
-        },
     },
+    native_coverage: false,
 }
 
 // NOTE: This is a compile time test, and does not need to be
diff --git a/libs/input/tests/InputChannel_test.cpp b/libs/input/tests/InputChannel_test.cpp
index 60feb53..25356cf 100644
--- a/libs/input/tests/InputChannel_test.cpp
+++ b/libs/input/tests/InputChannel_test.cpp
@@ -16,8 +16,6 @@
 
 #include <array>
 
-#include "TestHelpers.h"
-
 #include <unistd.h>
 #include <time.h>
 #include <errno.h>
@@ -67,11 +65,7 @@
 
     ASSERT_EQ(OK, result) << "should have successfully opened a channel pair";
 
-    // Name
-    EXPECT_STREQ("channel name (server)", serverChannel->getName().c_str())
-            << "server channel should have suffixed name";
-    EXPECT_STREQ("channel name (client)", clientChannel->getName().c_str())
-            << "client channel should have suffixed name";
+    EXPECT_EQ(serverChannel->getName(), clientChannel->getName());
 
     // Server->Client communication
     InputMessage serverMsg = {};
@@ -80,9 +74,10 @@
     EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
             << "server channel should be able to send message to client channel";
 
-    InputMessage clientMsg;
-    EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg))
+    android::base::Result<InputMessage> clientMsgResult = clientChannel->receiveMessage();
+    ASSERT_TRUE(clientMsgResult.ok())
             << "client channel should be able to receive message from server channel";
+    const InputMessage& clientMsg = *clientMsgResult;
     EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
             << "client channel should receive the correct message from server channel";
     EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
@@ -96,9 +91,10 @@
     EXPECT_EQ(OK, clientChannel->sendMessage(&clientReply))
             << "client channel should be able to send message to server channel";
 
-    InputMessage serverReply;
-    EXPECT_EQ(OK, serverChannel->receiveMessage(&serverReply))
+    android::base::Result<InputMessage> serverReplyResult = serverChannel->receiveMessage();
+    ASSERT_TRUE(serverReplyResult.ok())
             << "server channel should be able to receive message from client channel";
+    const InputMessage& serverReply = *serverReplyResult;
     EXPECT_EQ(clientReply.header.type, serverReply.header.type)
             << "server channel should receive the correct message from client channel";
     EXPECT_EQ(clientReply.header.seq, serverReply.header.seq)
@@ -136,9 +132,10 @@
             << "client channel should observe that message is available before receiving it";
 
     // Receive (consume) the message.
-    InputMessage clientMsg;
-    EXPECT_EQ(OK, receiverChannel->receiveMessage(&clientMsg))
+    android::base::Result<InputMessage> clientMsgResult = receiverChannel->receiveMessage();
+    ASSERT_TRUE(clientMsgResult.ok())
             << "client channel should be able to receive message from server channel";
+    const InputMessage& clientMsg = *clientMsgResult;
     EXPECT_EQ(serverMsg.header.type, clientMsg.header.type)
             << "client channel should receive the correct message from server channel";
     EXPECT_EQ(serverMsg.body.key.action, clientMsg.body.key.action)
@@ -158,8 +155,8 @@
     ASSERT_EQ(OK, result)
             << "should have successfully opened a channel pair";
 
-    InputMessage msg;
-    EXPECT_EQ(WOULD_BLOCK, clientChannel->receiveMessage(&msg))
+    android::base::Result<InputMessage> msgResult = clientChannel->receiveMessage();
+    EXPECT_EQ(WOULD_BLOCK, msgResult.error().code())
             << "receiveMessage should have returned WOULD_BLOCK";
 }
 
@@ -174,8 +171,8 @@
 
     serverChannel.reset(); // close server channel
 
-    InputMessage msg;
-    EXPECT_EQ(DEAD_OBJECT, clientChannel->receiveMessage(&msg))
+    android::base::Result<InputMessage> msgResult = clientChannel->receiveMessage();
+    EXPECT_EQ(DEAD_OBJECT, msgResult.error().code())
             << "receiveMessage should have returned DEAD_OBJECT";
 }
 
@@ -209,7 +206,7 @@
         MotionClassification::DEEP_PRESS,
     };
 
-    InputMessage serverMsg = {}, clientMsg;
+    InputMessage serverMsg = {};
     serverMsg.header.type = InputMessage::Type::MOTION;
     serverMsg.header.seq = 1;
     serverMsg.body.motion.pointerCount = 1;
@@ -220,11 +217,13 @@
         EXPECT_EQ(OK, serverChannel->sendMessage(&serverMsg))
                 << "server channel should be able to send message to client channel";
 
-        EXPECT_EQ(OK, clientChannel->receiveMessage(&clientMsg))
+        android::base::Result<InputMessage> clientMsgResult = clientChannel->receiveMessage();
+        ASSERT_TRUE(clientMsgResult.ok())
                 << "client channel should be able to receive message from server channel";
+        const InputMessage& clientMsg = *clientMsgResult;
         EXPECT_EQ(serverMsg.header.type, clientMsg.header.type);
-        EXPECT_EQ(classification, clientMsg.body.motion.classification) <<
-                "Expected to receive " << motionClassificationToString(classification);
+        EXPECT_EQ(classification, clientMsg.body.motion.classification)
+                << "Expected to receive " << motionClassificationToString(classification);
     }
 }
 
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
new file mode 100644
index 0000000..d708316
--- /dev/null
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -0,0 +1,247 @@
+/**
+ * Copyright 2024 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 <input/InputConsumerNoResampling.h>
+
+#include <memory>
+#include <optional>
+
+#include <TestEventMatchers.h>
+#include <TestInputChannel.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
+#include <input/InputEventBuilders.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+namespace {
+
+using std::chrono::nanoseconds;
+
+using ::testing::AllOf;
+using ::testing::Matcher;
+using ::testing::Not;
+
+} // namespace
+
+class InputConsumerTest : public testing::Test, public InputConsumerCallbacks {
+protected:
+    InputConsumerTest()
+          : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
+            mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+        Looper::setForThread(mLooper);
+        mConsumer =
+                std::make_unique<InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+                                                            std::make_unique<LegacyResampler>());
+    }
+
+    void invokeLooperCallback() const {
+        sp<LooperCallback> callback;
+        ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+                                             /*events=*/nullptr, &callback, /*data=*/nullptr));
+        callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+    }
+
+    void assertOnBatchedInputEventPendingWasCalled() {
+        ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL)
+                << "onBatchedInputEventPending has not been called.";
+        --mOnBatchedInputEventPendingInvocationCount;
+    }
+
+    void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
+        std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop();
+        ASSERT_NE(motionEvent, nullptr);
+        EXPECT_THAT(*motionEvent, matcher);
+    }
+
+    std::shared_ptr<TestInputChannel> mClientTestChannel;
+    sp<Looper> mLooper;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+    BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+private:
+    size_t mOnBatchedInputEventPendingInvocationCount{0};
+
+    // InputConsumerCallbacks interface
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "should deterministically have input because there is a batch";
+        }
+        ++mOnBatchedInputEventPendingInvocationCount;
+    };
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+};
+
+TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .eventTime(nanoseconds{0ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .eventTime(nanoseconds{5ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .eventTime(nanoseconds{10ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+
+    assertOnBatchedInputEventPendingWasCalled();
+
+    mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
+
+    std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(downMotionEvent, nullptr);
+
+    std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(moveMotionEvent, nullptr);
+    EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 3UL);
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+}
+
+TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) {
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .eventTime(nanoseconds{0ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .eventTime(nanoseconds{5ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .eventTime(nanoseconds{10ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
+                                               .eventTime(nanoseconds{15ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+
+    assertOnBatchedInputEventPendingWasCalled();
+
+    mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
+
+    std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(downMotionEvent, nullptr);
+
+    std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(moveMotionEvent, nullptr);
+    const size_t numSamples = moveMotionEvent->getHistorySize() + 1;
+    EXPECT_LT(moveMotionEvent->getHistoricalEventTime(numSamples - 2),
+              moveMotionEvent->getEventTime());
+
+    // Consume all remaining events before ending the test. Otherwise, the smart pointer that owns
+    // consumer is set to null before destroying consumer. This leads to a member function call on a
+    // null object.
+    // TODO(b/332613662): Remove this workaround.
+    mConsumer->consumeBatchedInputEvents(std::nullopt);
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, true);
+}
+
+TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) {
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/4}
+                                               .deviceId(1)
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/5}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_UP)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                    Not(MotionEventIsResampled())));
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+} // namespace android
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index a965573..a67e1ef 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -21,15 +21,43 @@
 #include <attestation/HmacKeyManager.h>
 #include <binder/Parcel.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
+#include <input/InputEventBuilders.h>
 
 namespace android {
 
-// Default display id.
-static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+namespace {
 
-static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+// Default display id.
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+
+constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
+constexpr auto POINTER_0_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+constexpr auto POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+constexpr auto POINTER_0_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+constexpr auto POINTER_1_UP =
+        AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+std::array<float, 9> asFloat9(const ui::Transform& t) {
+    std::array<float, 9> mat{};
+    mat[0] = t[0][0];
+    mat[1] = t[1][0];
+    mat[2] = t[2][0];
+    mat[3] = t[0][1];
+    mat[4] = t[1][1];
+    mat[5] = t[2][1];
+    mat[6] = t[0][2];
+    mat[7] = t[1][2];
+    mat[8] = t[2][2];
+    return mat;
+}
 
 class BaseTest : public testing::Test {
 protected:
@@ -38,6 +66,8 @@
                                                      22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
 };
 
+} // namespace
+
 // --- PointerCoordsTest ---
 
 class PointerCoordsTest : public BaseTest {
@@ -216,7 +246,7 @@
     ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
 
     // Set display id.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     event.setDisplayId(newDisplayId);
     ASSERT_EQ(newDisplayId, event.getDisplayId());
 }
@@ -332,15 +362,17 @@
 }
 
 void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
+    const int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
-                      AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
-                      AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
-                      MotionClassification::NONE, mTransform, 2.0f, 2.1f,
-                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                      mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
-                      mPointerProperties, mSamples[0].pointerCoords);
-    event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords);
-    event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords);
+                      AMOTION_EVENT_ACTION_MOVE, 0, flags, AMOTION_EVENT_EDGE_FLAG_TOP,
+                      AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY, MotionClassification::NONE,
+                      mTransform, 2.0f, 2.1f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                      AMOTION_EVENT_INVALID_CURSOR_POSITION, mRawTransform, ARBITRARY_DOWN_TIME,
+                      ARBITRARY_EVENT_TIME, 2, mPointerProperties, mSamples[0].pointerCoords);
+    event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords, event->getId());
+    event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords, event->getId());
 }
 
 void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) {
@@ -352,14 +384,19 @@
     ASSERT_EQ(DISPLAY_ID, event->getDisplayId());
     EXPECT_EQ(HMAC, event->getHmac());
     ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, event->getAction());
-    ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED, event->getFlags());
+    ASSERT_EQ(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION,
+              event->getFlags());
     ASSERT_EQ(AMOTION_EVENT_EDGE_FLAG_TOP, event->getEdgeFlags());
     ASSERT_EQ(AMETA_ALT_ON, event->getMetaState());
     ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, event->getButtonState());
     ASSERT_EQ(MotionClassification::NONE, event->getClassification());
     EXPECT_EQ(mTransform, event->getTransform());
-    ASSERT_EQ(X_OFFSET, event->getXOffset());
-    ASSERT_EQ(Y_OFFSET, event->getYOffset());
+    ASSERT_NEAR((-RAW_X_OFFSET / RAW_X_SCALE) * X_SCALE + X_OFFSET, event->getRawXOffset(),
+                EPSILON);
+    ASSERT_NEAR((-RAW_Y_OFFSET / RAW_Y_SCALE) * Y_SCALE + Y_OFFSET, event->getRawYOffset(),
+                EPSILON);
     ASSERT_EQ(2.0f, event->getXPrecision());
     ASSERT_EQ(2.1f, event->getYPrecision());
     ASSERT_EQ(ARBITRARY_DOWN_TIME, event->getDownTime());
@@ -513,7 +550,7 @@
     ASSERT_EQ(AINPUT_SOURCE_JOYSTICK, event.getSource());
 
     // Set displayId.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     event.setDisplayId(newDisplayId);
     ASSERT_EQ(newDisplayId, event.getDisplayId());
 
@@ -554,25 +591,184 @@
     ASSERT_EQ(event.getX(0), copy.getX(0));
 }
 
+TEST_F(MotionEventTest, CheckEventIdWithHistoryIsIncremented) {
+    MotionEvent event;
+    constexpr int32_t ARBITRARY_ID = 42;
+    event.initialize(ARBITRARY_ID, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, INVALID_HMAC,
+                     AMOTION_EVENT_ACTION_MOVE, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
+                     AMOTION_EVENT_BUTTON_PRIMARY, MotionClassification::NONE, mTransform, 0, 0,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                     mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
+                     mPointerProperties, mSamples[0].pointerCoords);
+    ASSERT_EQ(event.getId(), ARBITRARY_ID);
+    event.addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords, ARBITRARY_ID + 1);
+    ASSERT_EQ(event.getId(), ARBITRARY_ID + 1);
+    event.addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords, ARBITRARY_ID + 2);
+    ASSERT_EQ(event.getId(), ARBITRARY_ID + 2);
+}
+
+TEST_F(MotionEventTest, SplitPointerDown) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitDown;
+    std::bitset<MAX_POINTER_ID + 1> splitDownIds{};
+    splitDownIds.set(6, true);
+    splitDown.splitFrom(event, splitDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitDown.getAction(), AMOTION_EVENT_ACTION_DOWN);
+    ASSERT_EQ(splitDown.getPointerCount(), 1u);
+    ASSERT_EQ(splitDown.getPointerId(0), 6);
+    ASSERT_EQ(splitDown.getX(0), 6);
+    ASSERT_EQ(splitDown.getY(0), 6);
+
+    MotionEvent splitPointerDown;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerDownIds{};
+    splitPointerDownIds.set(6, true);
+    splitPointerDownIds.set(8, true);
+    splitPointerDown.splitFrom(event, splitPointerDownIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerDown.getAction(), POINTER_0_DOWN);
+    ASSERT_EQ(splitPointerDown.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerDown.getPointerId(0), 6);
+    ASSERT_EQ(splitPointerDown.getX(0), 6);
+    ASSERT_EQ(splitPointerDown.getY(0), 6);
+    ASSERT_EQ(splitPointerDown.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerDown.getX(1), 8);
+    ASSERT_EQ(splitPointerDown.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 1u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), 4);
+    ASSERT_EQ(splitMove.getY(0), 4);
+}
+
+TEST_F(MotionEventTest, SplitPointerUp) {
+    MotionEvent event = MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(4, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_UP);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 4);
+    ASSERT_EQ(splitUp.getX(0), 4);
+    ASSERT_EQ(splitUp.getY(0), 4);
+
+    MotionEvent splitPointerUp;
+    std::bitset<MAX_POINTER_ID + 1> splitPointerUpIds{};
+    splitPointerUpIds.set(4, true);
+    splitPointerUpIds.set(8, true);
+    splitPointerUp.splitFrom(event, splitPointerUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitPointerUp.getAction(), POINTER_0_UP);
+    ASSERT_EQ(splitPointerUp.getPointerCount(), 2u);
+    ASSERT_EQ(splitPointerUp.getPointerId(0), 4);
+    ASSERT_EQ(splitPointerUp.getX(0), 4);
+    ASSERT_EQ(splitPointerUp.getY(0), 4);
+    ASSERT_EQ(splitPointerUp.getPointerId(1), 8);
+    ASSERT_EQ(splitPointerUp.getX(1), 8);
+    ASSERT_EQ(splitPointerUp.getY(1), 8);
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(6, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/43);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 6);
+    ASSERT_EQ(splitMove.getX(0), 6);
+    ASSERT_EQ(splitMove.getY(0), 6);
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), 8);
+    ASSERT_EQ(splitMove.getY(1), 8);
+}
+
+TEST_F(MotionEventTest, SplitPointerUpCancel) {
+    MotionEvent event = MotionEventBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .addFlag(AMOTION_EVENT_FLAG_CANCELED)
+                                .build();
+
+    MotionEvent splitUp;
+    std::bitset<MAX_POINTER_ID + 1> splitUpIds{};
+    splitUpIds.set(6, true);
+    splitUp.splitFrom(event, splitUpIds, /*eventId=*/42);
+    ASSERT_EQ(splitUp.getAction(), AMOTION_EVENT_ACTION_CANCEL);
+    ASSERT_EQ(splitUp.getPointerCount(), 1u);
+    ASSERT_EQ(splitUp.getPointerId(0), 6);
+    ASSERT_EQ(splitUp.getX(0), 6);
+    ASSERT_EQ(splitUp.getY(0), 6);
+}
+
+TEST_F(MotionEventTest, SplitPointerMove) {
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .pointer(PointerBuilder(/*id=*/6, ToolType::FINGER).x(6).y(6))
+                                .pointer(PointerBuilder(/*id=*/8, ToolType::FINGER).x(8).y(8))
+                                .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                                .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                                .build();
+
+    MotionEvent splitMove;
+    std::bitset<MAX_POINTER_ID + 1> splitMoveIds{};
+    splitMoveIds.set(4, true);
+    splitMoveIds.set(8, true);
+    splitMove.splitFrom(event, splitMoveIds, /*eventId=*/42);
+    ASSERT_EQ(splitMove.getAction(), AMOTION_EVENT_ACTION_MOVE);
+    ASSERT_EQ(splitMove.getPointerCount(), 2u);
+    ASSERT_EQ(splitMove.getPointerId(0), 4);
+    ASSERT_EQ(splitMove.getX(0), event.getX(0));
+    ASSERT_EQ(splitMove.getY(0), event.getY(0));
+    ASSERT_EQ(splitMove.getRawX(0), event.getRawX(0));
+    ASSERT_EQ(splitMove.getRawY(0), event.getRawY(0));
+    ASSERT_EQ(splitMove.getPointerId(1), 8);
+    ASSERT_EQ(splitMove.getX(1), event.getX(2));
+    ASSERT_EQ(splitMove.getY(1), event.getY(2));
+    ASSERT_EQ(splitMove.getRawX(1), event.getRawX(2));
+    ASSERT_EQ(splitMove.getRawY(1), event.getRawY(2));
+}
+
 TEST_F(MotionEventTest, OffsetLocation) {
     MotionEvent event;
     initializeEventWithHistory(&event);
+    const float xOffset = event.getRawXOffset();
+    const float yOffset = event.getRawYOffset();
 
     event.offsetLocation(5.0f, -2.0f);
 
-    ASSERT_EQ(X_OFFSET + 5.0f, event.getXOffset());
-    ASSERT_EQ(Y_OFFSET - 2.0f, event.getYOffset());
+    ASSERT_EQ(xOffset + 5.0f, event.getRawXOffset());
+    ASSERT_EQ(yOffset - 2.0f, event.getRawYOffset());
 }
 
 TEST_F(MotionEventTest, Scale) {
     MotionEvent event;
     initializeEventWithHistory(&event);
     const float unscaledOrientation = event.getOrientation(0);
+    const float unscaledXOffset = event.getRawXOffset();
+    const float unscaledYOffset = event.getRawYOffset();
 
     event.scale(2.0f);
 
-    ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
-    ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
+    ASSERT_EQ(unscaledXOffset * 2, event.getRawXOffset());
+    ASSERT_EQ(unscaledYOffset * 2, event.getRawYOffset());
 
     ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON);
     ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON);
@@ -642,8 +838,10 @@
     }
     MotionEvent event;
     ui::Transform identityTransform;
+    const int32_t flags = AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, /*flags=*/0,
+                     INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0, flags,
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
                      MotionClassification::NONE, identityTransform, /*xPrecision=*/0,
                      /*yPrecision=*/0, /*xCursorPosition=*/3 + RADIUS, /*yCursorPosition=*/2,
@@ -701,11 +899,10 @@
     pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, dy);
     nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
     MotionEvent event;
-    event.initialize(InputEvent::nextId(), /* deviceId */ 1, source,
-                     /* displayId */ 0, INVALID_HMAC, action,
-                     /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
-                     /* buttonState */ 0, MotionClassification::NONE, transform,
-                     /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/1, source, ui::LogicalDisplayId::DEFAULT,
+                     INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0,
+                     AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, transform,
+                     /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, rawTransform, eventTime, eventTime,
                      pointerCoords.size(), pointerProperties.data(), pointerCoords.data());
     return event;
@@ -931,4 +1128,90 @@
     ASSERT_EQ(EXPECTED.y, event.getYCursorPosition());
 }
 
+TEST_F(MotionEventTest, InvalidOrientationNotRotated) {
+    // This touch event does not have a value for AXIS_ORIENTATION, and the flags are implicitly
+    // set to 0. The transform is set to a 90-degree rotation.
+    MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                .downTime(ARBITRARY_DOWN_TIME)
+                                .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                                .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                                .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                                .build();
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+    event.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_EQ(event.getOrientation(/*pointerIndex=*/0), 0.f);
+}
+
+TEST_F(MotionEventTest, ValidZeroOrientationRotated) {
+    // This touch events will implicitly have a value of 0 for its AXIS_ORIENTATION.
+    auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                           .downTime(ARBITRARY_DOWN_TIME)
+                           .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER).x(4).y(4))
+                           .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                           .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                           .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
+    MotionEvent nonDirectionalEvent = builder.build();
+    MotionEvent directionalEvent =
+            builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build();
+
+    // The angle is rotated by the initial transform, a 90-degree rotation.
+    ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), M_PI_2, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+    ASSERT_NEAR(fabs(directionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), 0.f, EPSILON);
+
+    nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_NEAR(fabs(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0)), M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), -M_PI_2, EPSILON);
+}
+
+TEST_F(MotionEventTest, ValidNonZeroOrientationRotated) {
+    const float initial = 1.f;
+    auto builder = MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                           .downTime(ARBITRARY_DOWN_TIME)
+                           .pointer(PointerBuilder(/*id=*/4, ToolType::FINGER)
+                                            .x(4)
+                                            .y(4)
+                                            .axis(AMOTION_EVENT_AXIS_ORIENTATION, initial))
+                           .transform(ui::Transform(ui::Transform::ROT_90, 100, 100))
+                           .rawTransform(ui::Transform(ui::Transform::FLIP_H, 50, 50))
+                           .addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
+
+    MotionEvent nonDirectionalEvent = builder.build();
+    MotionEvent directionalEvent =
+            builder.addFlag(AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION).build();
+
+    // The angle is rotated by the initial transform, a 90-degree rotation.
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial + M_PI_2, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_90, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI, EPSILON);
+
+    nonDirectionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    directionalEvent.transform(asFloat9(ui::Transform(ui::Transform::ROT_180, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial, EPSILON);
+
+    nonDirectionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    directionalEvent.applyTransform(asFloat9(ui::Transform(ui::Transform::ROT_270, 100, 100)));
+    ASSERT_NEAR(nonDirectionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+    ASSERT_NEAR(directionalEvent.getOrientation(/*pointerIndex=*/0), initial - M_PI_2, EPSILON);
+}
+
 } // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
new file mode 100644
index 0000000..1210f71
--- /dev/null
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -0,0 +1,941 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/logging.h>
+#include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
+#include <input/InputConsumerNoResampling.h>
+#include <input/InputTransport.h>
+
+using android::base::Result;
+
+namespace android {
+
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+static constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+static constexpr int32_t POINTER_1_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+static constexpr int32_t POINTER_2_DOWN =
+        AMOTION_EVENT_ACTION_POINTER_DOWN | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+static auto constexpr TIMEOUT = 5s;
+
+struct Pointer {
+    int32_t id;
+    float x;
+    float y;
+    bool isResampled = false;
+};
+
+// A collection of arguments to be sent as publishMotionEvent(). The saved members of this struct
+// allow to check the expectations against the event acquired from the InputConsumerCallbacks. To
+// help simplify expectation checking it carries members not present in MotionEvent, like
+// |rawXScale|.
+struct PublishMotionArgs {
+    const int32_t action;
+    const nsecs_t downTime;
+    const uint32_t seq;
+    int32_t eventId;
+    const int32_t deviceId = 1;
+    const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
+    const int32_t actionButton = 0;
+    const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
+    const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    const int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
+    const MotionClassification classification = MotionClassification::AMBIGUOUS_GESTURE;
+    const float xScale = 2;
+    const float yScale = 3;
+    const float xOffset = -10;
+    const float yOffset = -20;
+    const float rawXScale = 4;
+    const float rawYScale = -5;
+    const float rawXOffset = -11;
+    const float rawYOffset = 42;
+    const float xPrecision = 0.25;
+    const float yPrecision = 0.5;
+    const float xCursorPosition = 1.3;
+    const float yCursorPosition = 50.6;
+    std::array<uint8_t, 32> hmac;
+    int32_t flags;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    const nsecs_t eventTime;
+    size_t pointerCount;
+    std::vector<PointerProperties> pointerProperties;
+    std::vector<PointerCoords> pointerCoords;
+
+    PublishMotionArgs(int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers,
+                      const uint32_t seq);
+};
+
+PublishMotionArgs::PublishMotionArgs(int32_t inAction, nsecs_t inDownTime,
+                                     const std::vector<Pointer>& pointers, const uint32_t inSeq)
+      : action(inAction),
+        downTime(inDownTime),
+        seq(inSeq),
+        eventId(InputEvent::nextId()),
+        eventTime(systemTime(SYSTEM_TIME_MONOTONIC)) {
+    hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
+            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
+
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    pointerCount = pointers.size();
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties.push_back({});
+        pointerProperties[i].clear();
+        pointerProperties[i].id = pointers[i].id;
+        pointerProperties[i].toolType = ToolType::FINGER;
+
+        pointerCoords.push_back({});
+        pointerCoords[i].clear();
+        pointerCoords[i].isResampled = pointers[i].isResampled;
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 1.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.5 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 2.7 * i);
+        pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
+    }
+    transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+}
+
+// Checks expectations against |motionEvent| acquired from an InputConsumer. Floating point
+// comparisons limit precision to EPSILON.
+void verifyArgsEqualToEvent(const PublishMotionArgs& args, const MotionEvent& motionEvent) {
+    EXPECT_EQ(args.eventId, motionEvent.getId());
+    EXPECT_EQ(args.deviceId, motionEvent.getDeviceId());
+    EXPECT_EQ(args.source, motionEvent.getSource());
+    EXPECT_EQ(args.displayId, motionEvent.getDisplayId());
+    EXPECT_EQ(args.hmac, motionEvent.getHmac());
+    EXPECT_EQ(args.action, motionEvent.getAction());
+    EXPECT_EQ(args.downTime, motionEvent.getDownTime());
+    EXPECT_EQ(args.flags, motionEvent.getFlags());
+    EXPECT_EQ(args.edgeFlags, motionEvent.getEdgeFlags());
+    EXPECT_EQ(args.metaState, motionEvent.getMetaState());
+    EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
+    EXPECT_EQ(args.classification, motionEvent.getClassification());
+    EXPECT_EQ(args.transform, motionEvent.getTransform());
+    EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+                motionEvent.getRawXOffset(), EPSILON);
+    EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+                motionEvent.getRawYOffset(), EPSILON);
+    EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
+    EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
+    EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.yCursorPosition, motionEvent.getRawYCursorPosition(), EPSILON);
+    EXPECT_NEAR(args.xCursorPosition * args.xScale + args.xOffset, motionEvent.getXCursorPosition(),
+                EPSILON);
+    EXPECT_NEAR(args.yCursorPosition * args.yScale + args.yOffset, motionEvent.getYCursorPosition(),
+                EPSILON);
+    EXPECT_EQ(args.rawTransform, motionEvent.getRawTransform());
+    EXPECT_EQ(args.eventTime, motionEvent.getEventTime());
+    EXPECT_EQ(args.pointerCount, motionEvent.getPointerCount());
+    EXPECT_EQ(0U, motionEvent.getHistorySize());
+
+    for (size_t i = 0; i < args.pointerCount; i++) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(args.pointerProperties[i].id, motionEvent.getPointerId(i));
+        EXPECT_EQ(args.pointerProperties[i].toolType, motionEvent.getToolType(i));
+
+        const auto& pc = args.pointerCoords[i];
+        EXPECT_EQ(pc, motionEvent.getSamplePointerCoords()[i]);
+
+        EXPECT_NEAR(pc.getX() * args.rawXScale + args.rawXOffset, motionEvent.getRawX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.rawYScale + args.rawYOffset, motionEvent.getRawY(i), EPSILON);
+        EXPECT_NEAR(pc.getX() * args.xScale + args.xOffset, motionEvent.getX(i), EPSILON);
+        EXPECT_NEAR(pc.getY() * args.yScale + args.yOffset, motionEvent.getY(i), EPSILON);
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent.getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent.getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent.getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent.getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent.getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent.getToolMinor(i));
+
+        // Calculate the orientation after scaling, keeping in mind that an orientation of 0 is
+        // "up", and the positive y direction is "down".
+        const float unscaledOrientation = pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION);
+        const float x = sinf(unscaledOrientation) * args.xScale;
+        const float y = -cosf(unscaledOrientation) * args.yScale;
+        EXPECT_EQ(atan2f(x, -y), motionEvent.getOrientation(i));
+    }
+}
+
+void publishMotionEvent(InputPublisher& publisher, const PublishMotionArgs& a) {
+    status_t status =
+            publisher.publishMotionEvent(a.seq, a.eventId, a.deviceId, a.source, a.displayId,
+                                         a.hmac, a.action, a.actionButton, a.flags, a.edgeFlags,
+                                         a.metaState, a.buttonState, a.classification, a.transform,
+                                         a.xPrecision, a.yPrecision, a.xCursorPosition,
+                                         a.yCursorPosition, a.rawTransform, a.downTime, a.eventTime,
+                                         a.pointerCount, a.pointerProperties.data(),
+                                         a.pointerCoords.data());
+    ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
+}
+
+Result<InputPublisher::ConsumerResponse> receiveConsumerResponse(
+        InputPublisher& publisher, std::chrono::milliseconds timeout) {
+    const std::chrono::time_point start = std::chrono::steady_clock::now();
+
+    while (true) {
+        Result<InputPublisher::ConsumerResponse> result = publisher.receiveConsumerResponse();
+        if (result.ok()) {
+            return result;
+        }
+        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+        if (waited > timeout) {
+            return result;
+        }
+    }
+}
+
+void verifyFinishedSignal(InputPublisher& publisher, uint32_t seq, nsecs_t publishTime) {
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(publisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse returned " << result.error().message();
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
+} // namespace
+
+class InputConsumerMessageHandler : public MessageHandler {
+public:
+    InputConsumerMessageHandler(std::function<void(const Message&)> function)
+          : mFunction(function) {}
+
+private:
+    void handleMessage(const Message& message) override { mFunction(message); }
+
+    std::function<void(const Message&)> mFunction;
+};
+
+class InputPublisherAndConsumerNoResamplingTest : public testing::Test,
+                                                  public InputConsumerCallbacks {
+protected:
+    std::unique_ptr<InputChannel> mClientChannel;
+    std::unique_ptr<InputPublisher> mPublisher;
+    std::unique_ptr<InputConsumerNoResampling> mConsumer;
+
+    std::thread mLooperThread;
+    sp<Looper> mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+
+    // LOOPER CONTROL
+    // Set to false when you want the looper to exit
+    std::atomic<bool> mExitLooper = false;
+    std::mutex mLock;
+
+    // Used by test to notify looper that the value of "mLooperMayProceed" has changed
+    std::condition_variable mNotifyLooperMayProceed;
+    bool mLooperMayProceed GUARDED_BY(mLock){true};
+    // Used by looper to notify the test that it's about to block on "mLooperMayProceed" -> true
+    std::condition_variable mNotifyLooperWaiting;
+    bool mLooperIsBlocked GUARDED_BY(mLock){false};
+
+    std::condition_variable mNotifyConsumerDestroyed;
+    bool mConsumerDestroyed GUARDED_BY(mLock){false};
+
+    void runLooper() {
+        static constexpr int LOOP_INDEFINITELY = -1;
+        Looper::setForThread(mLooper);
+        // Loop forever -- this thread is dedicated to servicing the looper callbacks.
+        while (!mExitLooper) {
+            mLooper->pollOnce(/*timeoutMillis=*/LOOP_INDEFINITELY);
+        }
+    }
+
+    void SetUp() override {
+        std::unique_ptr<InputChannel> serverChannel;
+        status_t result =
+                InputChannel::openInputChannelPair("channel name", serverChannel, mClientChannel);
+        ASSERT_EQ(OK, result);
+
+        mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+        mMessageHandler = sp<InputConsumerMessageHandler>::make(
+                [this](const Message& message) { handleMessage(message); });
+        mLooperThread = std::thread([this] { runLooper(); });
+        sendMessage(LooperMessage::CREATE_CONSUMER);
+    }
+
+    void publishAndConsumeKeyEvent();
+    void publishAndConsumeMotionStream();
+    void publishAndConsumeMotionDown(nsecs_t downTime);
+    void publishAndConsumeSinglePointerMultipleSamples(const size_t nSamples);
+    void publishAndConsumeBatchedMotionMove(nsecs_t downTime);
+    void publishAndConsumeFocusEvent();
+    void publishAndConsumeCaptureEvent();
+    void publishAndConsumeDragEvent();
+    void publishAndConsumeTouchModeEvent();
+    void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+                                      const std::vector<Pointer>& pointers);
+
+    void TearDown() override {
+        // Destroy the consumer, flushing any of the pending ack's.
+        sendMessage(LooperMessage::DESTROY_CONSUMER);
+        {
+            std::unique_lock lock(mLock);
+            base::ScopedLockAssertion assumeLocked(mLock);
+            mNotifyConsumerDestroyed.wait(lock, [this] { return mConsumerDestroyed; });
+        }
+        // Stop the looper thread so that we can destroy the object.
+        mExitLooper = true;
+        mLooper->wake();
+        mLooperThread.join();
+    }
+
+protected:
+    // Interaction with the looper thread
+    enum class LooperMessage : int {
+        CALL_PROBABLY_HAS_INPUT,
+        CREATE_CONSUMER,
+        DESTROY_CONSUMER,
+        CALL_REPORT_TIMELINE,
+        BLOCK_LOOPER,
+    };
+    void sendMessage(LooperMessage message);
+    struct ReportTimelineArgs {
+        int32_t inputEventId;
+        nsecs_t gpuCompletedTime;
+        nsecs_t presentTime;
+    };
+    // The input to the function "InputConsumer::reportTimeline". Populated on the test thread and
+    // accessed on the looper thread.
+    BlockingQueue<ReportTimelineArgs> mReportTimelineArgs;
+    // The output of calling "InputConsumer::probablyHasInput()". Populated on the looper thread and
+    // accessed on the test thread.
+    BlockingQueue<bool> mProbablyHasInputResponses;
+
+private:
+    sp<MessageHandler> mMessageHandler;
+    void handleMessage(const Message& message);
+
+    static auto constexpr NO_EVENT_TIMEOUT = 10ms;
+    // The sequence number to use when publishing the next event
+    uint32_t mSeq = 1;
+
+    BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
+    BlockingQueue<std::unique_ptr<MotionEvent>> mMotionEvents;
+    BlockingQueue<std::unique_ptr<FocusEvent>> mFocusEvents;
+    BlockingQueue<std::unique_ptr<CaptureEvent>> mCaptureEvents;
+    BlockingQueue<std::unique_ptr<DragEvent>> mDragEvents;
+    BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
+
+    // InputConsumerCallbacks interface
+    void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
+        mKeyEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onMotionEvent(std::unique_ptr<MotionEvent> event, uint32_t seq) override {
+        mMotionEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onBatchedInputEventPending(int32_t pendingBatchSource) override {
+        if (!mConsumer->probablyHasInput()) {
+            ADD_FAILURE() << "should deterministically have input because there is a batch";
+        }
+        mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
+    };
+    void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
+        mFocusEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onCaptureEvent(std::unique_ptr<CaptureEvent> event, uint32_t seq) override {
+        mCaptureEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+    void onDragEvent(std::unique_ptr<DragEvent> event, uint32_t seq) override {
+        mDragEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    }
+    void onTouchModeEvent(std::unique_ptr<TouchModeEvent> event, uint32_t seq) override {
+        mTouchModeEvents.push(std::move(event));
+        mConsumer->finishInputEvent(seq, true);
+    };
+};
+
+void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
+    Message msg{ftl::to_underlying(message)};
+    mLooper->sendMessage(mMessageHandler, msg);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::handleMessage(const Message& message) {
+    switch (static_cast<LooperMessage>(message.what)) {
+        case LooperMessage::CALL_PROBABLY_HAS_INPUT: {
+            mProbablyHasInputResponses.push(mConsumer->probablyHasInput());
+            break;
+        }
+        case LooperMessage::CREATE_CONSUMER: {
+            mConsumer =
+                    std::make_unique<InputConsumerNoResampling>(std::move(mClientChannel), mLooper,
+                                                                *this, /*resampler=*/nullptr);
+            break;
+        }
+        case LooperMessage::DESTROY_CONSUMER: {
+            mConsumer = nullptr;
+            {
+                std::unique_lock lock(mLock);
+                mConsumerDestroyed = true;
+            }
+            mNotifyConsumerDestroyed.notify_all();
+            break;
+        }
+        case LooperMessage::CALL_REPORT_TIMELINE: {
+            std::optional<ReportTimelineArgs> args = mReportTimelineArgs.pop();
+            if (!args.has_value()) {
+                ADD_FAILURE() << "Couldn't get the 'reportTimeline' args in time";
+                return;
+            }
+            mConsumer->reportTimeline(args->inputEventId, args->gpuCompletedTime,
+                                      args->presentTime);
+            break;
+        }
+        case LooperMessage::BLOCK_LOOPER: {
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = true;
+            }
+            mNotifyLooperWaiting.notify_all();
+
+            {
+                std::unique_lock lock(mLock);
+                base::ScopedLockAssertion assumeLocked(mLock);
+                mNotifyLooperMayProceed.wait(lock, [this] { return mLooperMayProceed; });
+            }
+
+            {
+                std::unique_lock lock(mLock);
+                mLooperIsBlocked = false;
+            }
+            mNotifyLooperWaiting.notify_all();
+            break;
+        }
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeKeyEvent() {
+    status_t status;
+
+    const uint32_t seq = mSeq++;
+    int32_t eventId = InputEvent::nextId();
+    constexpr int32_t deviceId = 1;
+    constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
+    constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
+    constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
+                                              20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
+                                              9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
+    constexpr int32_t action = AKEY_EVENT_ACTION_DOWN;
+    constexpr int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
+    constexpr int32_t keyCode = AKEYCODE_ENTER;
+    constexpr int32_t scanCode = 13;
+    constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
+    constexpr int32_t repeatCount = 1;
+    constexpr nsecs_t downTime = 3;
+    constexpr nsecs_t eventTime = 4;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishKeyEvent(seq, eventId, deviceId, source, displayId, hmac, action,
+                                         flags, keyCode, scanCode, metaState, repeatCount, downTime,
+                                         eventTime);
+    ASSERT_EQ(OK, status) << "publisher publishKeyEvent should return OK";
+
+    std::optional<std::unique_ptr<KeyEvent>> optKeyEvent = mKeyEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optKeyEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<KeyEvent> keyEvent = std::move(*optKeyEvent);
+
+    sendMessage(LooperMessage::CALL_PROBABLY_HAS_INPUT);
+    std::optional<bool> probablyHasInput = mProbablyHasInputResponses.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(probablyHasInput.has_value());
+    ASSERT_FALSE(probablyHasInput.value()) << "no events should be waiting after being consumed";
+
+    EXPECT_EQ(eventId, keyEvent->getId());
+    EXPECT_EQ(deviceId, keyEvent->getDeviceId());
+    EXPECT_EQ(source, keyEvent->getSource());
+    EXPECT_EQ(displayId, keyEvent->getDisplayId());
+    EXPECT_EQ(hmac, keyEvent->getHmac());
+    EXPECT_EQ(action, keyEvent->getAction());
+    EXPECT_EQ(flags, keyEvent->getFlags());
+    EXPECT_EQ(keyCode, keyEvent->getKeyCode());
+    EXPECT_EQ(scanCode, keyEvent->getScanCode());
+    EXPECT_EQ(metaState, keyEvent->getMetaState());
+    EXPECT_EQ(repeatCount, keyEvent->getRepeatCount());
+    EXPECT_EQ(downTime, keyEvent->getDownTime());
+    EXPECT_EQ(eventTime, keyEvent->getEventTime());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionStream() {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionDown(nsecs_t downTime) {
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+}
+
+/*
+ * Decompose a potential multi-sampled MotionEvent into multiple MotionEvents
+ * with a single sample.
+ */
+std::vector<MotionEvent> splitBatchedMotionEvent(const MotionEvent& batchedMotionEvent) {
+    std::vector<MotionEvent> singleMotionEvents;
+    const size_t batchSize = batchedMotionEvent.getHistorySize() + 1;
+    for (size_t i = 0; i < batchSize; ++i) {
+        MotionEvent singleMotionEvent;
+        singleMotionEvent
+                .initialize(batchedMotionEvent.getId(), batchedMotionEvent.getDeviceId(),
+                            batchedMotionEvent.getSource(), batchedMotionEvent.getDisplayId(),
+                            batchedMotionEvent.getHmac(), batchedMotionEvent.getAction(),
+                            batchedMotionEvent.getActionButton(), batchedMotionEvent.getFlags(),
+                            batchedMotionEvent.getEdgeFlags(), batchedMotionEvent.getMetaState(),
+                            batchedMotionEvent.getButtonState(),
+                            batchedMotionEvent.getClassification(),
+                            batchedMotionEvent.getTransform(), batchedMotionEvent.getXPrecision(),
+                            batchedMotionEvent.getYPrecision(),
+                            batchedMotionEvent.getRawXCursorPosition(),
+                            batchedMotionEvent.getRawYCursorPosition(),
+                            batchedMotionEvent.getRawTransform(), batchedMotionEvent.getDownTime(),
+                            batchedMotionEvent.getHistoricalEventTime(/*historicalIndex=*/i),
+                            batchedMotionEvent.getPointerCount(),
+                            batchedMotionEvent.getPointerProperties(),
+                            (batchedMotionEvent.getSamplePointerCoords() + i));
+        singleMotionEvents.push_back(singleMotionEvent);
+    }
+    return singleMotionEvents;
+}
+
+/*
+ * Simulates a single pointer touching the screen and leaving it there for a period of time.
+ * Publishes a DOWN event and consumes it right away. Then, publishes a sequence of MOVE
+ * samples for the same pointer, and waits until it has been consumed. Splits batched MotionEvents
+ * into individual samples. Checks the consumed MotionEvents against the published ones.
+ * This test is non-deterministic because it depends on the timing of arrival of events to the
+ * socket.
+ *
+ * @param nSamples The number of MOVE samples to publish before attempting consumption.
+ */
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeSinglePointerMultipleSamples(
+        const size_t nSamples) {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    const Pointer pointer(0, 20, 30);
+
+    const PublishMotionArgs argsDown(AMOTION_EVENT_ACTION_DOWN, downTime, {pointer}, mSeq);
+    const nsecs_t publishTimeOfDown = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, argsDown);
+
+    // Consume the DOWN event.
+    ASSERT_TRUE(mMotionEvents.popWithTimeout(TIMEOUT).has_value());
+
+    verifyFinishedSignal(*mPublisher, mSeq, publishTimeOfDown);
+
+    std::vector<nsecs_t> publishTimes;
+    std::vector<PublishMotionArgs> argsMoves;
+    std::queue<uint32_t> publishedSequenceNumbers;
+
+    // Block Looper to increase the chance of batching events
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock l(mLock);
+        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
+    }
+
+    uint32_t firstSampleId;
+    for (size_t i = 0; i < nSamples; ++i) {
+        publishedSequenceNumbers.push(++mSeq);
+        PublishMotionArgs argsMove(AMOTION_EVENT_ACTION_MOVE, downTime, {pointer}, mSeq);
+        // A batched MotionEvent only has a single event id, currently determined when the
+        // MotionEvent is initialized. Therefore, to pass the eventId comparisons inside
+        // verifyArgsEqualToEvent, we need to override the event id of the published args to match
+        // the event id of the first sample inside the MotionEvent.
+        if (i == 0) {
+            firstSampleId = argsMove.eventId;
+        }
+        argsMove.eventId = firstSampleId;
+        publishTimes.push_back(systemTime(SYSTEM_TIME_MONOTONIC));
+        argsMoves.push_back(argsMove);
+        publishMotionEvent(*mPublisher, argsMove);
+    }
+
+    std::vector<MotionEvent> singleSampledMotionEvents;
+
+    // Unblock Looper
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+
+    // We have no control over the socket behavior, so the consumer can receive
+    // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
+    // mix of those)
+    while (singleSampledMotionEvents.size() != nSamples) {
+        const std::optional<std::unique_ptr<MotionEvent>> batchedMotionEvent =
+                mMotionEvents.popWithTimeout(TIMEOUT);
+        // The events received by these calls are never null
+        std::vector<MotionEvent> splitMotionEvents = splitBatchedMotionEvent(**batchedMotionEvent);
+        singleSampledMotionEvents.insert(singleSampledMotionEvents.end(), splitMotionEvents.begin(),
+                                         splitMotionEvents.end());
+    }
+
+    // Consumer can choose to finish events in any order. For simplicity,
+    // we verify the events in sequence (since that is how the test is implemented).
+    for (size_t i = 0; i < nSamples; ++i) {
+        verifyArgsEqualToEvent(argsMoves[i], singleSampledMotionEvents[i]);
+        verifyFinishedSignal(*mPublisher, publishedSequenceNumbers.front(), publishTimes[i]);
+        publishedSequenceNumbers.pop();
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeBatchedMotionMove(
+        nsecs_t downTime) {
+    uint32_t seq = mSeq++;
+    const std::vector<Pointer> pointers = {Pointer{.id = 0, .x = 20, .y = 30}};
+    PublishMotionArgs args(AMOTION_EVENT_ACTION_MOVE, downTime, pointers, seq);
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    // Block the looper thread, preventing it from being able to service any of the fd callbacks.
+
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock lock(mLock);
+        mNotifyLooperWaiting.wait(lock, [this] { return mLooperIsBlocked; });
+    }
+
+    publishMotionEvent(*mPublisher, args);
+
+    // Ensure no event arrives because the UI thread is blocked
+    std::optional<std::unique_ptr<MotionEvent>> noEvent =
+            mMotionEvents.popWithTimeout(NO_EVENT_TIMEOUT);
+    ASSERT_FALSE(noEvent.has_value()) << "Got unexpected event: " << *noEvent;
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_FALSE(result.ok());
+    ASSERT_EQ(WOULD_BLOCK, result.error().code());
+
+    // We shouldn't be calling mConsumer on the UI thread, but in this situation, the looper
+    // thread is locked, so this should be safe to do.
+    ASSERT_TRUE(mConsumer->probablyHasInput())
+            << "should deterministically have input because there is a batch";
+
+    // Now, unblock the looper thread, so that the event can arrive.
+    {
+        std::scoped_lock lock(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> motion = std::move(*optMotion);
+    ASSERT_EQ(ACTION_MOVE, motion->getAction());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeMotionEvent(
+        int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
+    uint32_t seq = mSeq++;
+    PublishMotionArgs args(action, downTime, pointers, seq);
+    nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    publishMotionEvent(*mPublisher, args);
+
+    std::optional<std::unique_ptr<MotionEvent>> optMotion = mMotionEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optMotion.has_value());
+    std::unique_ptr<MotionEvent> event = std::move(*optMotion);
+
+    verifyArgsEqualToEvent(args, *event);
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeFocusEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool hasFocus = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishFocusEvent(seq, eventId, hasFocus);
+    ASSERT_EQ(OK, status) << "publisher publishFocusEvent should return OK";
+
+    std::optional<std::unique_ptr<FocusEvent>> optFocusEvent = mFocusEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optFocusEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<FocusEvent> focusEvent = std::move(*optFocusEvent);
+    EXPECT_EQ(eventId, focusEvent->getId());
+    EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeCaptureEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 42;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool captureEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishCaptureEvent(seq, eventId, captureEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishCaptureEvent should return OK";
+
+    std::optional<std::unique_ptr<CaptureEvent>> optEvent = mCaptureEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<CaptureEvent> event = std::move(*optEvent);
+
+    const CaptureEvent& captureEvent = *event;
+    EXPECT_EQ(eventId, captureEvent.getId());
+    EXPECT_EQ(captureEnabled, captureEvent.getPointerCaptureEnabled());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeDragEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool isExiting = false;
+    constexpr float x = 10;
+    constexpr float y = 15;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishDragEvent(seq, eventId, x, y, isExiting);
+    ASSERT_EQ(OK, status) << "publisher publishDragEvent should return OK";
+
+    std::optional<std::unique_ptr<DragEvent>> optEvent = mDragEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value()) << "consumer should have returned non-NULL event";
+    std::unique_ptr<DragEvent> event = std::move(*optEvent);
+
+    const DragEvent& dragEvent = *event;
+    EXPECT_EQ(eventId, dragEvent.getId());
+    EXPECT_EQ(isExiting, dragEvent.isExiting());
+    EXPECT_EQ(x, dragEvent.getX());
+    EXPECT_EQ(y, dragEvent.getY());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+void InputPublisherAndConsumerNoResamplingTest::publishAndConsumeTouchModeEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool touchModeEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+    std::optional<std::unique_ptr<TouchModeEvent>> optEvent =
+            mTouchModeEvents.popWithTimeout(TIMEOUT);
+    ASSERT_TRUE(optEvent.has_value());
+    std::unique_ptr<TouchModeEvent> event = std::move(*optEvent);
+
+    const TouchModeEvent& touchModeEvent = *event;
+    EXPECT_EQ(eventId, touchModeEvent.getId());
+    EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+    verifyFinishedSignal(*mPublisher, seq, publishTime);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
+    const int32_t inputEventId = 20;
+    const nsecs_t gpuCompletedTime = 30;
+    const nsecs_t presentTime = 40;
+
+    mReportTimelineArgs.emplace(inputEventId, gpuCompletedTime, presentTime);
+    sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+
+    Result<InputPublisher::ConsumerResponse> result = receiveConsumerResponse(*mPublisher, TIMEOUT);
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Timeline>(*result));
+    const InputPublisher::Timeline& timeline = std::get<InputPublisher::Timeline>(*result);
+    ASSERT_EQ(inputEventId, timeline.inputEventId);
+    ASSERT_EQ(gpuCompletedTime, timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]);
+    ASSERT_EQ(presentTime, timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishKeyEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMotionMoveEvent_EndToEnd) {
+    // Publish a DOWN event before MOVE to pass the InputVerifier checks.
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionDown(downTime));
+
+    // Publish the MOVE event and check expectations.
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeBatchedMotionMove(downTime));
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishFocusEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishCaptureEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishDragEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishTouchModeEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = 0;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest,
+       PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+    status_t status;
+    const size_t pointerCount = MAX_POINTERS + 1;
+    PointerProperties pointerProperties[pointerCount];
+    PointerCoords pointerCoords[pointerCount];
+    for (size_t i = 0; i < pointerCount; i++) {
+        pointerProperties[i].clear();
+        pointerCoords[i].clear();
+    }
+
+    ui::Transform identityTransform;
+    status =
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishMultipleEvents_EndToEnd) {
+    const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+    publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+    // Provide a consistent input stream - cancel the gesture that was started above
+    publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+                                 {Pointer{.id = 0, .x = 20, .y = 30},
+                                  Pointer{.id = 1, .x = 200, .y = 300},
+                                  Pointer{.id = 2, .x = 200, .y = 300}});
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
+}
+
+TEST_F(InputPublisherAndConsumerNoResamplingTest, PublishAndConsumeSinglePointer) {
+    publishAndConsumeSinglePointerMultipleSamples(3);
+}
+
+} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 3543020..e65a919 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -14,11 +14,9 @@
  * limitations under the License.
  */
 
-#include "TestHelpers.h"
-
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 using android::base::Result;
@@ -50,7 +48,7 @@
     const int32_t eventId;
     const int32_t deviceId = 1;
     const uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
-    const int32_t displayId = ADISPLAY_ID_DEFAULT;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
     const int32_t actionButton = 0;
     const int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
     const int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
@@ -91,7 +89,9 @@
     hmac = {0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15,
             16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
 
-    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+    flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+            AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
     if (action == AMOTION_EVENT_ACTION_CANCEL) {
         flags |= AMOTION_EVENT_FLAG_CANCELED;
     }
@@ -135,8 +135,10 @@
     EXPECT_EQ(args.buttonState, motionEvent.getButtonState());
     EXPECT_EQ(args.classification, motionEvent.getClassification());
     EXPECT_EQ(args.transform, motionEvent.getTransform());
-    EXPECT_EQ(args.xOffset, motionEvent.getXOffset());
-    EXPECT_EQ(args.yOffset, motionEvent.getYOffset());
+    EXPECT_NEAR((-args.rawXOffset / args.rawXScale) * args.xScale + args.xOffset,
+                motionEvent.getRawXOffset(), EPSILON);
+    EXPECT_NEAR((-args.rawYOffset / args.rawYScale) * args.yScale + args.yOffset,
+                motionEvent.getRawYOffset(), EPSILON);
     EXPECT_EQ(args.xPrecision, motionEvent.getXPrecision());
     EXPECT_EQ(args.yPrecision, motionEvent.getYPrecision());
     EXPECT_NEAR(args.xCursorPosition, motionEvent.getRawXCursorPosition(), EPSILON);
@@ -262,7 +264,7 @@
     int32_t eventId = InputEvent::nextId();
     constexpr int32_t deviceId = 1;
     constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
-    constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
+    constexpr ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT;
     constexpr std::array<uint8_t, 32> hmac = {31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21,
                                               20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10,
                                               9,  8,  7,  6,  5,  4,  3,  2,  1,  0};
@@ -622,13 +624,13 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenPointerCountLessThan1_ReturnsError) {
@@ -639,17 +641,17 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest,
-        PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
+       PublishMotionEvent_WhenPointerCountGreaterThanMax_ReturnsError) {
     status_t status;
     const size_t pointerCount = MAX_POINTERS + 1;
     PointerProperties pointerProperties[pointerCount];
@@ -661,13 +663,13 @@
 
     ui::Transform identityTransform;
     status =
-            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
-                                           0, 0, 0, MotionClassification::NONE, identityTransform,
-                                           0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+            mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0,
+                                           ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, 0, 0, 0, 0,
+                                           0, 0, MotionClassification::NONE, identityTransform, 0,
+                                           0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                            0, 0, pointerCount, pointerProperties, pointerCoords);
-    ASSERT_EQ(BAD_VALUE, status)
-            << "publisher publishMotionEvent should return BAD_VALUE";
+    ASSERT_EQ(BAD_VALUE, status) << "publisher publishMotionEvent should return BAD_VALUE";
 }
 
 TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
index 31cc145..0542f39 100644
--- a/libs/input/tests/MotionPredictorMetricsManager_test.cpp
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -167,7 +167,8 @@
                         .y(predictionPoints[i].position[0])
                         .axis(AMOTION_EVENT_AXIS_PRESSURE, predictionPoints[i].pressure)
                         .buildCoords();
-        predictionEvent.addSample(predictionPoints[i].targetTimestamp, &coords);
+        predictionEvent.addSample(predictionPoints[i].targetTimestamp, &coords,
+                                  predictionEvent.getId());
     }
     return predictionEvent;
 }
@@ -238,14 +239,17 @@
 
 // --- Ground-truth-generation helper functions. ---
 
+// Generates numPoints ground truth points with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given inputInterval.
 std::vector<GroundTruthPoint> generateConstantGroundTruthPoints(
-        const GroundTruthPoint& groundTruthPoint, size_t numPoints) {
+        const GroundTruthPoint& groundTruthPoint, size_t numPoints,
+        nsecs_t inputInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<GroundTruthPoint> groundTruthPoints;
     nsecs_t timestamp = groundTruthPoint.timestamp;
     for (size_t i = 0; i < numPoints; ++i) {
         groundTruthPoints.emplace_back(groundTruthPoint);
         groundTruthPoints.back().timestamp = timestamp;
-        timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        timestamp += inputInterval;
     }
     return groundTruthPoints;
 }
@@ -280,7 +284,8 @@
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
     const std::vector<GroundTruthPoint> groundTruthPoints =
-            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3);
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3,
+                                              /*inputInterval=*/10);
 
     ASSERT_EQ(3u, groundTruthPoints.size());
     // First point.
@@ -290,11 +295,11 @@
     // Second point.
     EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+    EXPECT_EQ(groundTruthPoints[1].timestamp, groundTruthPoint.timestamp + 10);
     // Third point.
     EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position);
     EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure);
-    EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+    EXPECT_EQ(groundTruthPoints[2].timestamp, groundTruthPoint.timestamp + 20);
 }
 
 TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) {
@@ -333,16 +338,19 @@
 
 // --- Prediction-generation helper functions. ---
 
-// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint.
-std::vector<PredictionPoint> generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) {
+// Generates TEST_MAX_NUM_PREDICTIONS predictions with values equal to those of the given
+// GroundTruthPoint, and with consecutive timestamps separated by the given predictionInterval.
+std::vector<PredictionPoint> generateConstantPredictions(
+        const GroundTruthPoint& groundTruthPoint,
+        nsecs_t predictionInterval = TEST_PREDICTION_INTERVAL_NANOS) {
     std::vector<PredictionPoint> predictions;
-    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS;
+    nsecs_t predictionTimestamp = groundTruthPoint.timestamp + predictionInterval;
     for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
         predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position,
                                                .pressure = groundTruthPoint.pressure},
                                               .originTimestamp = groundTruthPoint.timestamp,
                                               .targetTimestamp = predictionTimestamp});
-        predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+        predictionTimestamp += predictionInterval;
     }
     return predictions;
 }
@@ -375,8 +383,9 @@
 TEST(GeneratePredictionsTest, GenerateConstantPredictions) {
     const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
                                             .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t predictionInterval = 10;
     const std::vector<PredictionPoint> predictionPoints =
-            generateConstantPredictions(groundTruthPoint);
+            generateConstantPredictions(groundTruthPoint, predictionInterval);
 
     ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
     for (size_t i = 0; i < predictionPoints.size(); ++i) {
@@ -385,8 +394,7 @@
         EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6));
         EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp);
         EXPECT_EQ(predictionPoints[i].targetTimestamp,
-                  groundTruthPoint.timestamp +
-                          static_cast<nsecs_t>(i + 1) * TEST_PREDICTION_INTERVAL_NANOS);
+                  TEST_INITIAL_TIMESTAMP + static_cast<nsecs_t>(i + 1) * predictionInterval);
     }
 }
 
@@ -678,12 +686,9 @@
 //  • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
 //  • predictionPoints: the first index points to a vector of predictions corresponding to the
 //    source ground truth point with the same index.
-//     - The first element should be empty, because there are not expected to be predictions until
-//       we have received 2 ground truth points.
-//     - The last element may be empty, because there will be no future ground truth points to
-//       associate with those predictions (if not empty, it will be ignored).
+//     - For empty prediction vectors, MetricsManager::onPredict will not be called.
 //     - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty
-//       prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
+//       prediction vectors (that is, excluding the first and last). Thus, groundTruthPoints and
 //       predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
 //
 // When the function returns, outReportedAtomFields will contain the reported AtomFields.
@@ -697,19 +702,12 @@
                                                  createMockReportAtomFunction(
                                                          outReportedAtomFields));
 
-    // Validate structure of groundTruthPoints and predictionPoints.
-    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
     ASSERT_GE(groundTruthPoints.size(), 2u);
-    ASSERT_EQ(predictionPoints[0].size(), 0u);
-    for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) {
-        SCOPED_TRACE(testing::Message() << "i = " << i);
-        ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS);
-    }
+    ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
 
-    // Pass ground truth points and predictions (for all except first and last ground truth).
     for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
         metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i]));
-        if ((i > 0) && (i + 1 < predictionPoints.size())) {
+        if (!predictionPoints[i].empty()) {
             metricsManager.onPredict(makeMotionEvent(predictionPoints[i]));
         }
     }
@@ -738,7 +736,7 @@
 // Perfect predictions test:
 //  • Input: constant input events, perfect predictions matching the input events.
 //  • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics.
-//    (For example, scale-invariant errors are only reported for the final time bucket.)
+//    (For example, scale-invariant errors are only reported for the last time bucket.)
 TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
     GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
                                       .timestamp = TEST_INITIAL_TIMESTAMP};
@@ -977,5 +975,35 @@
     }
 }
 
+// Robustness test:
+//  • Input: input events separated by a significantly greater time interval than the interval
+//    between predictions.
+//  • Expectation: the MetricsManager should not crash in this case. (No assertions are made about
+//    the resulting metrics.)
+//
+// In practice, this scenario could arise either if the input and prediction intervals are
+// mismatched, or if input events are missing (dropped or skipped for some reason).
+TEST(MotionPredictorMetricsManagerTest, MismatchedInputAndPredictionInterval) {
+    // Create two ground truth points separated by MAX_NUM_PREDICTIONS * PREDICTION_INTERVAL,
+    // so that the second ground truth point corresponds to the last prediction bucket. This
+    // ensures that the scale-invariant error codepath will be run, giving full code coverage.
+    GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(0.0f, 0.0f), .pressure = 0.5f},
+                                      .timestamp = TEST_INITIAL_TIMESTAMP};
+    const nsecs_t inputInterval = TEST_MAX_NUM_PREDICTIONS * TEST_PREDICTION_INTERVAL_NANOS;
+    const std::vector<GroundTruthPoint> groundTruthPoints =
+            generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/2, inputInterval);
+
+    // Create predictions separated by the prediction interval.
+    std::vector<std::vector<PredictionPoint>> predictionPoints;
+    for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
+        predictionPoints.push_back(
+                generateConstantPredictions(groundTruthPoints[i], TEST_PREDICTION_INTERVAL_NANOS));
+    }
+
+    // Test that we can run the MetricsManager without crashing.
+    std::vector<AtomFields> reportedAtomFields;
+    runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+}
+
 } // namespace
 } // namespace android
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 3343114..106e686 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
+// TODO(b/331815574): Decouple this test from assumed config values.
 #include <chrono>
+#include <cmath>
 
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <input/MotionPredictor.h>
 
@@ -55,9 +58,10 @@
     }
 
     ui::Transform identityTransform;
-    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT, {0},
-                     action, /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE,
-                     AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, identityTransform,
+    event.initialize(InputEvent::nextId(), deviceId, AINPUT_SOURCE_STYLUS,
+                     ui::LogicalDisplayId::DEFAULT, {0}, action, /*actionButton=*/0, /*flags=*/0,
+                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
+                     MotionClassification::NONE, identityTransform,
                      /*xPrecision=*/0.1,
                      /*yPrecision=*/0.2, /*xCursorPosition=*/280, /*yCursorPosition=*/540,
                      identityTransform, /*downTime=*/100, eventTime.count(), pointerCount,
@@ -65,6 +69,112 @@
     return event;
 }
 
+TEST(JerkTrackerTest, JerkReadiness) {
+    JerkTracker jerkTracker(/*normalizedDt=*/true, /*alpha=*/1);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/3, 35, 70);
+    EXPECT_TRUE(jerkTracker.jerkMagnitude());
+    jerkTracker.reset();
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+    jerkTracker.pushSample(/*timestamp=*/4, 30, 60);
+    EXPECT_FALSE(jerkTracker.jerkMagnitude());
+}
+
+TEST(JerkTrackerTest, JerkCalculationNormalizedDtTrue) {
+    const float alpha = .5;
+    JerkTracker jerkTracker(/*normalizedDt=*/true, alpha);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/3, 45, 70);
+    /**
+     * Jerk derivative table
+     * x:    20   25   30   45
+     * x':    5    5   15
+     * x'':   0   10
+     * x''': 10
+     *
+     * y:    50   53   60   70
+     * y':    3    7   10
+     * y'':   4    3
+     * y''': -1
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1));
+    jerkTracker.pushSample(/*timestamp=*/4, 20, 65);
+    /**
+     * (continuing from above table)
+     * x:    45 -> 20
+     * x':   15 -> -25
+     * x'':  10 -> -40
+     * x''': -50
+     *
+     * y:    70 -> 65
+     * y':   10 -> -5
+     * y'':  3 -> -15
+     * y''': -18
+     */
+    const float newJerk = (1 - alpha) * std::hypot(10, -1) + alpha * std::hypot(-50, -18);
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), newJerk);
+}
+
+TEST(JerkTrackerTest, JerkCalculationNormalizedDtFalse) {
+    const float alpha = .5;
+    JerkTracker jerkTracker(/*normalizedDt=*/false, alpha);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/10, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/20, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/30, 45, 70);
+    /**
+     * Jerk derivative table
+     * x:     20   25   30   45
+     * x':    .5   .5  1.5
+     * x'':    0   .1
+     * x''': .01
+     *
+     * y:       50   53   60   70
+     * y':      .3   .7    1
+     * y'':    .04  .03
+     * y''': -.001
+     */
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(.01, -.001));
+    jerkTracker.pushSample(/*timestamp=*/50, 20, 65);
+    /**
+     * (continuing from above table)
+     * x:    45 -> 20
+     * x':   1.5 -> -1.25 (delta above, divide by 20)
+     * x'':  .1 -> -.275 (delta above, divide by 10)
+     * x''': -.0375 (delta above, divide by 10)
+     *
+     * y:    70 -> 65
+     * y':   1 -> -.25 (delta above, divide by 20)
+     * y'':  .03 -> -.125 (delta above, divide by 10)
+     * y''': -.0155 (delta above, divide by 10)
+     */
+    const float newJerk = (1 - alpha) * std::hypot(.01, -.001) + alpha * std::hypot(-.0375, -.0155);
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), newJerk);
+}
+
+TEST(JerkTrackerTest, JerkCalculationAfterReset) {
+    JerkTracker jerkTracker(/*normalizedDt=*/true, /*alpha=*/1);
+    jerkTracker.pushSample(/*timestamp=*/0, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/1, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/2, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/3, 45, 70);
+    jerkTracker.pushSample(/*timestamp=*/4, 20, 65);
+    jerkTracker.reset();
+    jerkTracker.pushSample(/*timestamp=*/5, 20, 50);
+    jerkTracker.pushSample(/*timestamp=*/6, 25, 53);
+    jerkTracker.pushSample(/*timestamp=*/7, 30, 60);
+    jerkTracker.pushSample(/*timestamp=*/8, 45, 70);
+    EXPECT_FLOAT_EQ(jerkTracker.jerkMagnitude().value(), std::hypot(10, -1));
+}
+
 TEST(MotionPredictorTest, IsPredictionAvailable) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
@@ -94,18 +204,14 @@
 TEST(MotionPredictorTest, FollowsGesture) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return true /*enable prediction*/; });
+    predictor.record(getMotionEvent(DOWN, 3.75, 3, 20ms));
+    predictor.record(getMotionEvent(MOVE, 4.8, 3, 30ms));
+    predictor.record(getMotionEvent(MOVE, 6.2, 3, 40ms));
+    predictor.record(getMotionEvent(MOVE, 8, 3, 50ms));
+    EXPECT_NE(nullptr, predictor.predict(90 * NSEC_PER_MSEC));
 
-    // MOVE without a DOWN is ignored.
-    predictor.record(getMotionEvent(MOVE, 1, 3, 10ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(DOWN, 2, 5, 20ms));
-    predictor.record(getMotionEvent(MOVE, 2, 7, 30ms));
-    predictor.record(getMotionEvent(MOVE, 3, 9, 40ms));
-    EXPECT_NE(nullptr, predictor.predict(50 * NSEC_PER_MSEC));
-
-    predictor.record(getMotionEvent(UP, 4, 11, 50ms));
-    EXPECT_EQ(nullptr, predictor.predict(20 * NSEC_PER_MSEC));
+    predictor.record(getMotionEvent(UP, 10.25, 3, 60ms));
+    EXPECT_EQ(nullptr, predictor.predict(100 * NSEC_PER_MSEC));
 }
 
 TEST(MotionPredictorTest, MultipleDevicesNotSupported) {
@@ -147,6 +253,70 @@
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
 }
 
+TEST_WITH_FLAGS(
+        MotionPredictorTest, LowJerkNoPruning,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is low (0.05 normalized).
+    predictor.record(getMotionEvent(DOWN, 2, 7, 20ms));
+    predictor.record(getMotionEvent(MOVE, 2.75, 7, 30ms));
+    predictor.record(getMotionEvent(MOVE, 3.8, 7, 40ms));
+    predictor.record(getMotionEvent(MOVE, 5.2, 7, 50ms));
+    predictor.record(getMotionEvent(MOVE, 7, 7, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_NE(nullptr, predicted);
+    EXPECT_EQ(static_cast<size_t>(5), predicted->getHistorySize() + 1);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, HighJerkPredictionsPruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Jerk is incredibly high.
+    predictor.record(getMotionEvent(DOWN, 0, 5, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, 70, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, 139, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, 1421, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, 41233, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(90 * NSEC_PER_MSEC);
+    EXPECT_EQ(nullptr, predicted);
+}
+
+TEST_WITH_FLAGS(
+        MotionPredictorTest, MediumJerkPredictionsSomePruned,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            enable_prediction_pruning_via_jerk_thresholding))) {
+    MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+                              []() { return true /*enable prediction*/; });
+
+    // Create another instance of TfLiteMotionPredictorModel to read config details.
+    std::unique_ptr<TfLiteMotionPredictorModel> testTfLiteModel =
+            TfLiteMotionPredictorModel::create();
+    const float mediumJerk =
+            (testTfLiteModel->config().lowJerk + testTfLiteModel->config().highJerk) / 2;
+    const float a = 3; // initial acceleration
+    const float b = 4; // initial velocity
+    const float c = 5; // initial position
+    predictor.record(getMotionEvent(DOWN, 0, c, 20ms));
+    predictor.record(getMotionEvent(MOVE, 0, c + b, 30ms));
+    predictor.record(getMotionEvent(MOVE, 0, c + 2 * b + a, 40ms));
+    predictor.record(getMotionEvent(MOVE, 0, c + 3 * b + 3 * a + mediumJerk, 50ms));
+    predictor.record(getMotionEvent(MOVE, 0, c + 4 * b + 6 * a + 4 * mediumJerk, 60ms));
+    std::unique_ptr<MotionEvent> predicted = predictor.predict(82 * NSEC_PER_MSEC);
+    EXPECT_NE(nullptr, predicted);
+    // Halfway between LOW_JERK and HIGH_JERK means that half of the predictions
+    // will be pruned. If model prediction window is close enough to predict()
+    // call time window, then half of the model predictions (5/2 -> 2) will be
+    // ouputted.
+    EXPECT_EQ(static_cast<size_t>(3), predicted->getHistorySize() + 1);
+}
+
 using AtomFields = MotionPredictorMetricsManager::AtomFields;
 using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
 
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
new file mode 100644
index 0000000..26dee39
--- /dev/null
+++ b/libs/input/tests/Resampler_test.cpp
@@ -0,0 +1,873 @@
+/**
+ * Copyright 2024 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 <input/Resampler.h>
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <memory>
+#include <vector>
+
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <input/InputTransport.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+namespace {
+
+using namespace std::literals::chrono_literals;
+
+constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
+struct Pointer {
+    int32_t id{0};
+    ToolType toolType{ToolType::FINGER};
+    float x{0.0f};
+    float y{0.0f};
+    bool isResampled{false};
+    /**
+     * Converts from Pointer to PointerCoords. Enables calling LegacyResampler methods and
+     * assertions only with the relevant data for tests.
+     */
+    operator PointerCoords() const;
+};
+
+Pointer::operator PointerCoords() const {
+    PointerCoords pointerCoords;
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+    pointerCoords.isResampled = isResampled;
+    return pointerCoords;
+}
+
+struct InputSample {
+    std::chrono::milliseconds eventTime{0};
+    std::vector<Pointer> pointers{};
+
+    explicit InputSample(std::chrono::milliseconds eventTime, const std::vector<Pointer>& pointers)
+          : eventTime{eventTime}, pointers{pointers} {}
+    /**
+     * Converts from InputSample to InputMessage. Enables calling LegacyResampler methods only with
+     * the relevant data for tests.
+     */
+    operator InputMessage() const;
+};
+
+InputSample::operator InputMessage() const {
+    InputMessageBuilder messageBuilder =
+            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                    .eventTime(std::chrono::nanoseconds{eventTime}.count())
+                    .source(AINPUT_SOURCE_TOUCHSCREEN)
+                    .downTime(0);
+
+    for (const Pointer& pointer : pointers) {
+        messageBuilder.pointer(
+                PointerBuilder{pointer.id, pointer.toolType}.x(pointer.x).y(pointer.y).isResampled(
+                        pointer.isResampled));
+    }
+    return messageBuilder.build();
+}
+
+struct InputStream {
+    std::vector<InputSample> samples{};
+    int32_t action{0};
+    DeviceId deviceId{0};
+    /**
+     * Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
+     * the relevant data for tests.
+     */
+    operator MotionEvent() const;
+};
+
+InputStream::operator MotionEvent() const {
+    const InputSample& firstSample{*samples.begin()};
+    MotionEventBuilder motionEventBuilder =
+            MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
+                    .downTime(0)
+                    .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
+                    .deviceId(deviceId);
+    for (const Pointer& pointer : firstSample.pointers) {
+        const PointerBuilder pointerBuilder =
+                PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
+        motionEventBuilder.pointer(pointerBuilder);
+    }
+    MotionEvent motionEvent = motionEventBuilder.build();
+    const size_t numSamples = samples.size();
+    for (size_t i = 1; i < numSamples; ++i) {
+        std::vector<PointerCoords> pointersCoords{samples[i].pointers.begin(),
+                                                  samples[i].pointers.end()};
+        motionEvent.addSample(static_cast<std::chrono::nanoseconds>(samples[i].eventTime).count(),
+                              pointersCoords.data(), motionEvent.getId());
+    }
+    return motionEvent;
+}
+
+} // namespace
+
+/**
+ * The testing setup assumes an input rate of 200 Hz and a display rate of 60 Hz. This implies that
+ * input events are received every 5 milliseconds, while the display consumes batched events every
+ * ~16 milliseconds. The resampler's RESAMPLE_LATENCY constant determines the resample time, which
+ * is calculated as frameTime - RESAMPLE_LATENCY. resampleTime specifies the time used for
+ * resampling. For example, if the desired frame time consumption is ~16 milliseconds, the resample
+ * time would be ~11 milliseconds. Consequenly, the last added sample to the motion event has an
+ * event time of ~11 milliseconds. Note that there are specific scenarios where resampleMotionEvent
+ * is not called with a multiple of ~16 milliseconds. These cases are primarily for data addition
+ * or to test other functionalities of the resampler.
+ *
+ * Coordinates are calculated using linear interpolation (lerp) based on the last two available
+ * samples. Linear interpolation is defined as (a + alpha*(b - a)). Let t_b and t_a be the
+ * timestamps of samples a and b, respectively. The interpolation factor alpha is calculated as
+ * (resampleTime - t_a) / (t_b - t_a). The value of alpha determines whether the resampled
+ * coordinates are interpolated or extrapolated. If alpha falls within the semi-closed interval [0,
+ * 1), the coordinates are interpolated. If alpha is greater than or equal to 1, the coordinates are
+ * extrapolated.
+ *
+ * The timeline below depics an interpolation scenario
+ * -----------------------------------|---------|---------|---------|----------
+ *                                   10ms      11ms      15ms      16ms
+ *                                   MOVE       |        MOVE       |
+ *                                         resampleTime         frameTime
+ * Based on the timeline alpha is (11 - 10)/(15 - 10) = 1/5. Thus, coordinates are interpolated.
+ *
+ * The following timeline portrays an extrapolation scenario
+ * -------------------------|---------|---------|-------------------|----------
+ *                          5ms      10ms      11ms                16ms
+ *                          MOVE     MOVE       |                   |
+ *                                         resampleTime         frameTime
+ * Likewise, alpha = (11 - 5)/(10 - 5) = 6/5. Hence, coordinates are extrapolated.
+ *
+ * If a motion event was resampled, the tests will check that the following conditions are satisfied
+ * to guarantee resampling correctness:
+ * - The motion event metadata must not change.
+ * - The number of samples in the motion event must only increment by 1.
+ * - The resampled values must be at the end of motion event coordinates.
+ * - The rasamples values must be near the hand calculations.
+ * - The resampled time must be the most recent one in motion event.
+ */
+class ResamplerTest : public testing::Test {
+protected:
+    ResamplerTest() : mResampler(std::make_unique<LegacyResampler>()) {}
+
+    ~ResamplerTest() override {}
+
+    void SetUp() override {}
+
+    void TearDown() override {}
+
+    std::unique_ptr<Resampler> mResampler;
+
+    /**
+     * Checks that beforeCall and afterCall are equal except for the mutated attributes by addSample
+     * member function.
+     * @param beforeCall MotionEvent before passing it to resampleMotionEvent
+     * @param afterCall MotionEvent after passing it to resampleMotionEvent
+     */
+    void assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
+                                               const MotionEvent& afterCall);
+
+    /**
+     * Asserts the MotionEvent is resampled by checking an increment in history size and that the
+     * resampled coordinates are near the expected ones.
+     */
+    void assertMotionEventIsResampledAndCoordsNear(
+            const MotionEvent& original, const MotionEvent& resampled,
+            const std::vector<PointerCoords>& expectedCoords);
+
+    void assertMotionEventIsNotResampled(const MotionEvent& original,
+                                         const MotionEvent& notResampled);
+};
+
+void ResamplerTest::assertMotionEventMetaDataDidNotMutate(const MotionEvent& beforeCall,
+                                                          const MotionEvent& afterCall) {
+    EXPECT_EQ(beforeCall.getDeviceId(), afterCall.getDeviceId());
+    EXPECT_EQ(beforeCall.getAction(), afterCall.getAction());
+    EXPECT_EQ(beforeCall.getActionButton(), afterCall.getActionButton());
+    EXPECT_EQ(beforeCall.getButtonState(), afterCall.getButtonState());
+    EXPECT_EQ(beforeCall.getFlags(), afterCall.getFlags());
+    EXPECT_EQ(beforeCall.getEdgeFlags(), afterCall.getEdgeFlags());
+    EXPECT_EQ(beforeCall.getClassification(), afterCall.getClassification());
+    EXPECT_EQ(beforeCall.getPointerCount(), afterCall.getPointerCount());
+    EXPECT_EQ(beforeCall.getMetaState(), afterCall.getMetaState());
+    EXPECT_EQ(beforeCall.getSource(), afterCall.getSource());
+    EXPECT_EQ(beforeCall.getXPrecision(), afterCall.getXPrecision());
+    EXPECT_EQ(beforeCall.getYPrecision(), afterCall.getYPrecision());
+    EXPECT_EQ(beforeCall.getDownTime(), afterCall.getDownTime());
+    EXPECT_EQ(beforeCall.getDisplayId(), afterCall.getDisplayId());
+}
+
+void ResamplerTest::assertMotionEventIsResampledAndCoordsNear(
+        const MotionEvent& original, const MotionEvent& resampled,
+        const std::vector<PointerCoords>& expectedCoords) {
+    assertMotionEventMetaDataDidNotMutate(original, resampled);
+
+    const size_t originalSampleSize = original.getHistorySize() + 1;
+    const size_t resampledSampleSize = resampled.getHistorySize() + 1;
+    EXPECT_EQ(originalSampleSize + 1, resampledSampleSize);
+
+    const size_t numPointers = resampled.getPointerCount();
+    const size_t beginLatestSample = resampledSampleSize - 1;
+    for (size_t i = 0; i < numPointers; ++i) {
+        SCOPED_TRACE(i);
+        EXPECT_EQ(original.getPointerId(i), resampled.getPointerId(i));
+        EXPECT_EQ(original.getToolType(i), resampled.getToolType(i));
+
+        const PointerCoords& resampledCoords =
+                resampled.getSamplePointerCoords()[beginLatestSample * numPointers + i];
+
+        EXPECT_TRUE(resampledCoords.isResampled);
+        EXPECT_NEAR(expectedCoords[i].getX(), resampledCoords.getX(), EPSILON);
+        EXPECT_NEAR(expectedCoords[i].getY(), resampledCoords.getY(), EPSILON);
+    }
+}
+
+void ResamplerTest::assertMotionEventIsNotResampled(const MotionEvent& original,
+                                                    const MotionEvent& notResampled) {
+    assertMotionEventMetaDataDidNotMutate(original, notResampled);
+    const size_t originalSampleSize = original.getHistorySize() + 1;
+    const size_t notResampledSampleSize = notResampled.getHistorySize() + 1;
+    EXPECT_EQ(originalSampleSize, notResampledSampleSize);
+}
+
+TEST_F(ResamplerTest, NonResampledAxesArePreserved) {
+    constexpr float TOUCH_MAJOR_VALUE = 1.0f;
+
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    constexpr std::chrono::nanoseconds eventTime{10ms};
+    PointerCoords pointerCoords{};
+    pointerCoords.isResampled = false;
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 2.0f);
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 2.0f);
+    pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, TOUCH_MAJOR_VALUE);
+
+    motionEvent.addSample(eventTime.count(), &pointerCoords, motionEvent.getId());
+
+    const InputMessage futureSample =
+            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, SinglePointerNotEnoughDataToResample) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
+    MotionEvent motionFromFirstDevice =
+            InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
+                         InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE,
+                        .deviceId = 0};
+
+    mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
+
+    MotionEvent motionFromSecondDevice =
+            InputStream{{InputSample{11ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE,
+                        .deviceId = 1};
+    const MotionEvent originalMotionEvent = motionFromSecondDevice;
+
+    mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
+    // The MotionEvent should not be resampled because the second event came from a different device
+    // than the previous event.
+    assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
+}
+
+TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 1.2f,
+                                                       .y = 2.4f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooSmallInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{11ms, {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(10'500'000ns, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+/**
+ * Tests extrapolation given two MotionEvents with a single sample.
+ */
+TEST_F(ResamplerTest, SinglePointerSingleSampleExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms, {{.id = 0, .x = 3.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 3.4f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, SinglePointerMultipleSampleExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.2f,
+                                                       .y = 4.4f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooSmallExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{9ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerDeltaTooLargeExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{26ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(32ms, motionEvent, nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, SinglePointerResampleTimeTooFarExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms, {{.id = 0, .x = 1.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{25ms,
+                                     {{.id = 0, .x = 2.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(48ms, motionEvent, nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.id = 0,
+                                                       .x = 2.4f,
+                                                       .y = 4.8f,
+                                                       .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 2.2f, .y = 2.2f, .isResampled = true},
+                                               Pointer{.x = 3.2f, .y = 3.2f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerSingleSampleExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerMultipleSampleExtrapolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}},
+                         InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                         {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
+                                              {Pointer{.x = 1.4f, .y = 1.4f, .isResampled = true},
+                                               Pointer{.x = 2.4f, .y = 2.4f, .isResampled = true}});
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{25ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage secondFutureSample =
+            InputSample{30ms,
+                        {{.id = 0, .x = 5.0f, .y = 5.0f, .isResampled = false},
+                         {.id = 1, .x = 6.0f, .y = 6.0f, .isResampled = false},
+                         {.id = 2, .x = 7.0f, .y = 7.0f, .isResampled = false}}};
+
+    const MotionEvent originalSecondMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(32ms, secondMotionEvent, &secondFutureSample);
+
+    assertMotionEventIsResampledAndCoordsNear(originalSecondMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.8f, .y = 3.8f, .isResampled = true},
+                                               Pointer{.x = 4.8f, .y = 4.8f, .isResampled = true},
+                                               Pointer{.x = 5.8f, .y = 5.8f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerIncreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 5.0f, .y = 5.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 0, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 1, .x = 5.0f, .y = 5.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDecreaseNumPointersExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false},
+                                      {.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
+                                              {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
+                                               Pointer{.x = 4.4f, .y = 4.4f, .isResampled = true}});
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdOrderExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsInterpolation) {
+    MotionEvent motionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample =
+            InputSample{15ms,
+                        {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                         {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentIdsExtrapolation) {
+    MotionEvent firstMotionEvent =
+            InputStream{{InputSample{5ms,
+                                     {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false},
+                                      {.id = 1, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent =
+            InputStream{{InputSample{10ms,
+                                     {{.id = 1, .x = 4.0f, .y = 4.0f, .isResampled = false},
+                                      {.id = 2, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
+                        AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::FINGER,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::FINGER,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::STYLUS,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerDifferentToolTypeExtrapolation) {
+    MotionEvent firstMotionEvent = InputStream{{InputSample{5ms,
+                                                            {{.id = 0,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 1.0f,
+                                                              .y = 1.0f,
+                                                              .isResampled = false},
+                                                             {.id = 1,
+                                                              .toolType = ToolType::FINGER,
+                                                              .x = 2.0f,
+                                                              .y = 2.0f,
+                                                              .isResampled = false}}}},
+                                               AMOTION_EVENT_ACTION_MOVE};
+
+    mResampler->resampleMotionEvent(9ms, firstMotionEvent, /*futureSample=*/nullptr);
+
+    MotionEvent secondMotionEvent = InputStream{{InputSample{10ms,
+                                                             {{.id = 0,
+                                                               .toolType = ToolType::FINGER,
+                                                               .x = 1.0f,
+                                                               .y = 1.0f,
+                                                               .isResampled = false},
+                                                              {.id = 1,
+                                                               .toolType = ToolType::STYLUS,
+                                                               .x = 2.0f,
+                                                               .y = 2.0f,
+                                                               .isResampled = false}}}},
+                                                AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
+
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeInterpolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const InputMessage futureSample = InputSample{15ms,
+                                                  {{.id = 0,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 3.0,
+                                                    .y = 3.0,
+                                                    .isResampled = false},
+                                                   {.id = 1,
+                                                    .toolType = ToolType::PALM,
+                                                    .x = 4.0,
+                                                    .y = 4.0,
+                                                    .isResampled = false}}};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+
+TEST_F(ResamplerTest, MultiplePointerShouldNotResampleToolTypeExtrapolation) {
+    MotionEvent motionEvent = InputStream{{InputSample{5ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 1.0f,
+                                                         .y = 1.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 2.0f,
+                                                         .y = 2.0f,
+                                                         .isResampled = false}}},
+                                           InputSample{10ms,
+                                                       {{.id = 0,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 3.0f,
+                                                         .y = 3.0f,
+                                                         .isResampled = false},
+                                                        {.id = 1,
+                                                         .toolType = ToolType::PALM,
+                                                         .x = 4.0f,
+                                                         .y = 4.0f,
+                                                         .isResampled = false}}}},
+                                          AMOTION_EVENT_ACTION_MOVE};
+
+    const MotionEvent originalMotionEvent = motionEvent;
+
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
+
+    assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
+}
+} // namespace android
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
new file mode 100644
index 0000000..dd2e40c
--- /dev/null
+++ b/libs/input/tests/TestEventMatchers.h
@@ -0,0 +1,110 @@
+/**
+ * Copyright 2024 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 <ostream>
+
+#include <input/Input.h>
+
+namespace android {
+
+/**
+ * This file contains a copy of Matchers from .../inputflinger/tests/TestEventMatchers.h. Ideally,
+ * implementations must not be duplicated.
+ * TODO(b/365606513): Find a way to share TestEventMatchers.h between inputflinger and libinput.
+ */
+
+class WithDeviceIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithDeviceIdMatcher(DeviceId deviceId) : mDeviceId(deviceId) {}
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mDeviceId == event.getDeviceId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with device id " << mDeviceId; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong device id"; }
+
+private:
+    const DeviceId mDeviceId;
+};
+
+inline WithDeviceIdMatcher WithDeviceId(int32_t deviceId) {
+    return WithDeviceIdMatcher(deviceId);
+}
+
+class WithMotionActionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+        bool matches = mAction == event.getAction();
+        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
+            matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with motion action " << MotionEvent::actionToString(mAction);
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            *os << " and FLAG_CANCELED";
+        }
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+    const int32_t mAction;
+};
+
+inline WithMotionActionMatcher WithMotionAction(int32_t action) {
+    return WithMotionActionMatcher(action);
+}
+
+class MotionEventIsResampledMatcher {
+public:
+    using is_gtest_matcher = void;
+
+    bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream*) const {
+        const size_t numSamples = motionEvent.getHistorySize() + 1;
+        const size_t numPointers = motionEvent.getPointerCount();
+        if (numPointers <= 0 || numSamples <= 0) {
+            return false;
+        }
+        for (size_t i = 0; i < numPointers; ++i) {
+            const PointerCoords& pointerCoords =
+                    motionEvent.getSamplePointerCoords()[numSamples * numPointers + i];
+            if (!pointerCoords.isResampled) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "MotionEvent is resampled."; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "MotionEvent is not resampled."; }
+};
+
+inline MotionEventIsResampledMatcher MotionEventIsResampled() {
+    return MotionEventIsResampledMatcher();
+}
+} // namespace android
diff --git a/libs/input/tests/TestHelpers.h b/libs/input/tests/TestHelpers.h
deleted file mode 100644
index 343d81f..0000000
--- a/libs/input/tests/TestHelpers.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010 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 TESTHELPERS_H
-#define TESTHELPERS_H
-
-#include <unistd.h>
-
-#include <utils/threads.h>
-
-namespace android {
-
-class Pipe {
-public:
-    int sendFd;
-    int receiveFd;
-
-    Pipe() {
-        int fds[2];
-        ::pipe(fds);
-
-        receiveFd = fds[0];
-        sendFd = fds[1];
-    }
-
-    ~Pipe() {
-        if (sendFd != -1) {
-            ::close(sendFd);
-        }
-
-        if (receiveFd != -1) {
-            ::close(receiveFd);
-        }
-    }
-
-    status_t writeSignal() {
-        ssize_t nWritten = ::write(sendFd, "*", 1);
-        return nWritten == 1 ? 0 : -errno;
-    }
-
-    status_t readSignal() {
-        char buf[1];
-        ssize_t nRead = ::read(receiveFd, buf, 1);
-        return nRead == 1 ? 0 : nRead == 0 ? -EPIPE : -errno;
-    }
-};
-
-class DelayedTask : public Thread {
-    int mDelayMillis;
-
-public:
-    explicit DelayedTask(int delayMillis) : mDelayMillis(delayMillis) { }
-
-protected:
-    virtual ~DelayedTask() { }
-
-    virtual void doTask() = 0;
-
-    virtual bool threadLoop() {
-        usleep(mDelayMillis * 1000);
-        doTask();
-        return false;
-    }
-};
-
-} // namespace android
-
-#endif // TESTHELPERS_H
diff --git a/libs/input/tests/TestInputChannel.cpp b/libs/input/tests/TestInputChannel.cpp
new file mode 100644
index 0000000..26a0ca2
--- /dev/null
+++ b/libs/input/tests/TestInputChannel.cpp
@@ -0,0 +1,102 @@
+/**
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TestInputChannel"
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include <TestInputChannel.h>
+
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <array>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <binder/IBinder.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+namespace {
+
+/**
+ * Returns a stub file descriptor by opening a socket pair and closing one of the fds. The returned
+ * fd can be used to construct an InputChannel.
+ */
+base::unique_fd generateFileDescriptor() {
+    std::array<int, 2> kFileDescriptors;
+    LOG_IF(FATAL, ::socketpair(AF_UNIX, SOCK_SEQPACKET, 0, kFileDescriptors.data()) != 0)
+            << "TestInputChannel. Failed to create socket pair.";
+    LOG_IF(FATAL, ::close(kFileDescriptors[1]) != 0)
+            << "TestInputChannel. Failed to close file descriptor.";
+    return base::unique_fd{kFileDescriptors[0]};
+}
+} // namespace
+
+// --- TestInputChannel ---
+
+TestInputChannel::TestInputChannel(const std::string& name)
+      : InputChannel{name, generateFileDescriptor(), sp<BBinder>::make()} {}
+
+void TestInputChannel::enqueueMessage(const InputMessage& message) {
+    mReceivedMessages.push(message);
+}
+
+status_t TestInputChannel::sendMessage(const InputMessage* message) {
+    LOG_IF(FATAL, message == nullptr)
+            << "TestInputChannel " << getName() << ". No message was passed to sendMessage.";
+
+    mSentMessages.push(*message);
+    return OK;
+}
+
+base::Result<InputMessage> TestInputChannel::receiveMessage() {
+    if (mReceivedMessages.empty()) {
+        return base::Error(WOULD_BLOCK);
+    }
+    InputMessage message = mReceivedMessages.front();
+    mReceivedMessages.pop();
+    return message;
+}
+
+bool TestInputChannel::probablyHasInput() const {
+    return !mReceivedMessages.empty();
+}
+
+void TestInputChannel::assertFinishMessage(uint32_t seq, bool handled) {
+    ASSERT_FALSE(mSentMessages.empty())
+            << "TestInputChannel " << getName() << ". Cannot assert. mSentMessages is empty.";
+
+    const InputMessage& finishMessage = mSentMessages.front();
+
+    EXPECT_EQ(finishMessage.header.seq, seq)
+            << "TestInputChannel " << getName()
+            << ". Sequence mismatch. Message seq: " << finishMessage.header.seq
+            << " Expected seq: " << seq;
+
+    EXPECT_EQ(finishMessage.body.finished.handled, handled)
+            << "TestInputChannel " << getName()
+            << ". Handled value mismatch. Message val: " << std::boolalpha
+            << finishMessage.body.finished.handled << "Expected val: " << handled
+            << std::noboolalpha;
+    mSentMessages.pop();
+}
+
+void TestInputChannel::assertNoSentMessages() const {
+    ASSERT_TRUE(mSentMessages.empty());
+}
+} // namespace android
\ No newline at end of file
diff --git a/libs/input/tests/TestInputChannel.h b/libs/input/tests/TestInputChannel.h
new file mode 100644
index 0000000..43253ec
--- /dev/null
+++ b/libs/input/tests/TestInputChannel.h
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2024 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 <queue>
+#include <string>
+
+#include <android-base/result.h>
+#include <gtest/gtest.h>
+#include <input/InputTransport.h>
+#include <utils/Errors.h>
+
+namespace android {
+
+class TestInputChannel final : public InputChannel {
+public:
+    explicit TestInputChannel(const std::string& name);
+
+    /**
+     * Enqueues a message in mReceivedMessages.
+     */
+    void enqueueMessage(const InputMessage& message);
+
+    /**
+     * Pushes message to mSentMessages. In the default implementation, InputChannel sends messages
+     * through a file descriptor. TestInputChannel, on the contrary, stores sent messages in
+     * mSentMessages for assertion reasons.
+     */
+    status_t sendMessage(const InputMessage* message) override;
+
+    /**
+     * Returns an InputMessage from mReceivedMessages. This is done instead of retrieving data
+     * directly from fd.
+     */
+    base::Result<InputMessage> receiveMessage() override;
+
+    /**
+     * Returns if mReceivedMessages is not empty.
+     */
+    bool probablyHasInput() const override;
+
+    void assertFinishMessage(uint32_t seq, bool handled);
+
+    void assertNoSentMessages() const;
+
+private:
+    // InputMessages received by the endpoint.
+    std::queue<InputMessage> mReceivedMessages;
+    // InputMessages sent by the endpoint.
+    std::queue<InputMessage> mSentMessages;
+};
+} // namespace android
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index 1cb7f7b..8d8b530 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-#include "TestHelpers.h"
-
 #include <chrono>
 #include <vector>
 
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
+#include <input/InputConsumer.h>
 #include <input/InputTransport.h>
 
 using namespace std::chrono_literals;
@@ -85,10 +84,11 @@
         ADD_FAILURE() << "Downtime should be equal to 0 (hardcoded for convenience)";
     }
     return mPublisher->publishMotionEvent(mSeq++, InputEvent::nextId(), /*deviceId=*/1,
-                                          AINPUT_SOURCE_TOUCHSCREEN, /*displayId=*/0, INVALID_HMAC,
-                                          action, /*actionButton=*/0, /*flags=*/0, /*edgeFlags=*/0,
-                                          AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
-                                          identityTransform, /*xPrecision=*/0, /*yPrecision=*/0,
+                                          AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                                          INVALID_HMAC, action, /*actionButton=*/0, /*flags=*/0,
+                                          /*edgeFlags=*/0, AMETA_NONE, /*buttonState=*/0,
+                                          MotionClassification::NONE, identityTransform,
+                                          /*xPrecision=*/0, /*yPrecision=*/0,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                           AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform,
                                           downTime, eventTime, properties.size(), properties.data(),
@@ -297,10 +297,9 @@
 }
 
 /**
- * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
- * a resampled timestamp and should be marked as such.
+ * Stylus pointer coordinates are resampled.
  */
-TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+TEST_F(TouchResamplingTest, StylusEventIsResampled) {
     std::chrono::nanoseconds frameTime;
     std::vector<InputEventEntry> entries, expectedEntries;
 
@@ -330,15 +329,91 @@
             //      id  x   y
             {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
-            // A resampled event is generated, but the stylus coordinates are not resampled.
             {25ms,
-             {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+             {{0, 35, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
              AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
 
 /**
+ * Mouse pointer coordinates are resampled.
+ */
+TEST_F(TouchResamplingTest, MouseEventIsResampled) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::MOUSE}}, AMOTION_EVENT_ACTION_MOVE},
+            {25ms,
+             {{0, 35, 30, .toolType = ToolType::MOUSE, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
+ * Motion events with palm tool type are not resampled.
+ */
+TEST_F(TouchResamplingTest, PalmEventIsNotResampled) {
+    std::chrono::nanoseconds frameTime;
+    std::vector<InputEventEntry> entries, expectedEntries;
+
+    // Initial ACTION_DOWN should be separate, because the first consume event will only return
+    // InputEvent with a single action.
+    entries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 5ms;
+    expectedEntries = {
+            //      id  x   y
+            {0ms, {{0, 10, 20, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_DOWN},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+
+    // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+    entries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    publishInputEventEntries(entries);
+    frameTime = 35ms;
+    expectedEntries = {
+            //      id  x   y
+            {10ms, {{0, 20, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+            {20ms, {{0, 30, 30, .toolType = ToolType::PALM}}, AMOTION_EVENT_ACTION_MOVE},
+    };
+    consumeInputEventEntries(expectedEntries, frameTime);
+}
+
+/**
  * Event should not be resampled when sample time is equal to event time.
  */
 TEST_F(TouchResamplingTest, SampleTimeEqualsEventTime) {
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index f9ca280..f50a3cd 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -24,7 +24,6 @@
 #include <android-base/stringprintf.h>
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/VelocityTracker.h>
 
 using std::literals::chrono_literals::operator""ms;
@@ -34,7 +33,7 @@
 
 namespace android {
 
-constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; // default display id
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT; // default display id
 
 constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually defined tests
 
@@ -156,7 +155,7 @@
         MotionEvent event;
         ui::Transform identityTransform;
         event.initialize(InputEvent::nextId(), /*deviceId=*/5, AINPUT_SOURCE_ROTARY_ENCODER,
-                         ADISPLAY_ID_NONE, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL,
+                         ui::LogicalDisplayId::INVALID, INVALID_HMAC, AMOTION_EVENT_ACTION_SCROLL,
                          /*actionButton=*/0, /*flags=*/0, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
                          /*buttonState=*/0, MotionClassification::NONE, identityTransform,
                          /*xPrecision=*/0, /*yPrecision=*/0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index 277d74d..df5fe9d 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -16,7 +16,6 @@
 
 #include <attestation/HmacKeyManager.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 
 namespace android {
@@ -24,7 +23,7 @@
 static KeyEvent getKeyEventWithFlags(int32_t flags) {
     KeyEvent event;
     event.initialize(InputEvent::nextId(), /*deviceId=*/2, AINPUT_SOURCE_GAMEPAD,
-                     ADISPLAY_ID_DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, flags,
                      AKEYCODE_BUTTON_X, /*scanCode=*/121, AMETA_ALT_ON, /*repeatCount=*/1,
                      /*downTime=*/1000, /*eventTime=*/2000);
     return event;
@@ -44,10 +43,11 @@
     ui::Transform transform;
     transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1});
     ui::Transform identity;
-    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
-                     INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0, flags,
-                     AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, /*buttonState=*/0,
-                     MotionClassification::NONE, transform, /*xPrecision=*/0.1, /*yPrecision=*/0.2,
+    event.initialize(InputEvent::nextId(), /*deviceId=*/0, AINPUT_SOURCE_MOUSE,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+                     /*actionButton=*/0, flags, AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE,
+                     /*buttonState=*/0, MotionClassification::NONE, transform, /*xPrecision=*/0.1,
+                     /*yPrecision=*/0.2,
                      /*xCursorPosition=*/280, /*yCursorPosition=*/540, identity, /*downTime=*/100,
                      /*eventTime=*/200, pointerCount, pointerProperties, pointerCoords);
     return event;
diff --git a/libs/math/OWNERS b/libs/math/OWNERS
index 82ae422..08f0c5f 100644
--- a/libs/math/OWNERS
+++ b/libs/math/OWNERS
@@ -1,5 +1,4 @@
 mathias@google.com
-randolphs@google.com
 romainguy@google.com
 sumir@google.com
 jreck@google.com
diff --git a/libs/math/include/math/TVecHelpers.h b/libs/math/include/math/TVecHelpers.h
index 0dac662..7278d2d 100644
--- a/libs/math/include/math/TVecHelpers.h
+++ b/libs/math/include/math/TVecHelpers.h
@@ -620,15 +620,10 @@
 }  // namespace details
 }  // namespace android
 
-namespace std {
-    template<template<typename T> class VECTOR, typename T>
-    struct hash<VECTOR<T>> {
-        static constexpr bool IS_VECTOR =
-            std::is_base_of<android::details::TVecUnaryOperators<VECTOR, T>, VECTOR<T>>::value;
-
-        typename std::enable_if<IS_VECTOR, size_t>::type
-        operator()(const VECTOR<T>& v) const {
-            return v.hash();
-        }
-    };
-}
+#define TVECHELPERS_STD_HASH(VECTOR)                  \
+    template <typename T>                             \
+    struct std::hash<VECTOR<T>> {                     \
+        size_t operator()(const VECTOR<T>& v) const { \
+            return v.hash();                          \
+        }                                             \
+    }
diff --git a/libs/math/include/math/mat2.h b/libs/math/include/math/mat2.h
index 3e6cd4c..24c2bad 100644
--- a/libs/math/include/math/mat2.h
+++ b/libs/math/include/math/mat2.h
@@ -373,5 +373,7 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TMat22);
+
 #undef PURE
 #undef CONSTEXPR
diff --git a/libs/math/include/math/mat3.h b/libs/math/include/math/mat3.h
index 5c8a9b2..4647a60 100644
--- a/libs/math/include/math/mat3.h
+++ b/libs/math/include/math/mat3.h
@@ -436,5 +436,7 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TMat33);
+
 #undef PURE
 #undef CONSTEXPR
diff --git a/libs/math/include/math/mat4.h b/libs/math/include/math/mat4.h
index c630d97..c9e118a 100644
--- a/libs/math/include/math/mat4.h
+++ b/libs/math/include/math/mat4.h
@@ -590,5 +590,7 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TMat44);
+
 #undef PURE
 #undef CONSTEXPR
diff --git a/libs/math/include/math/quat.h b/libs/math/include/math/quat.h
index 07573c5..43c8038 100644
--- a/libs/math/include/math/quat.h
+++ b/libs/math/include/math/quat.h
@@ -187,6 +187,8 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TQuaternion);
+
 #pragma clang diagnostic pop
 
 #undef PURE
diff --git a/libs/math/include/math/vec2.h b/libs/math/include/math/vec2.h
index e0adb7f..909c77e 100644
--- a/libs/math/include/math/vec2.h
+++ b/libs/math/include/math/vec2.h
@@ -122,4 +122,6 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TVec2);
+
 #pragma clang diagnostic pop
diff --git a/libs/math/include/math/vec3.h b/libs/math/include/math/vec3.h
index 21fb684..ff2b3e4 100644
--- a/libs/math/include/math/vec3.h
+++ b/libs/math/include/math/vec3.h
@@ -128,4 +128,6 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TVec3);
+
 #pragma clang diagnostic pop
diff --git a/libs/math/include/math/vec4.h b/libs/math/include/math/vec4.h
index 1e279fe..16509c9 100644
--- a/libs/math/include/math/vec4.h
+++ b/libs/math/include/math/vec4.h
@@ -125,4 +125,6 @@
 // ----------------------------------------------------------------------------------------
 }  // namespace android
 
+TVECHELPERS_STD_HASH(android::details::TVec4);
+
 #pragma clang diagnostic pop
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 8f005a5..bed31e2 100644
--- a/libs/nativedisplay/AChoreographer.cpp
+++ b/libs/nativedisplay/AChoreographer.cpp
@@ -148,29 +148,31 @@
 void AChoreographer_postFrameCallback(AChoreographer* choreographer,
                                       AChoreographer_frameCallback callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0);
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
                                              AChoreographer_frameCallback callback, void* data,
                                              long delayMillis) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis));
+            ->postFrameCallbackDelayed(callback, nullptr, nullptr, data, ms2ns(delayMillis),
+                                       CALLBACK_ANIMATION);
 }
 void AChoreographer_postVsyncCallback(AChoreographer* choreographer,
                                       AChoreographer_vsyncCallback callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0);
+            ->postFrameCallbackDelayed(nullptr, nullptr, callback, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
                                         AChoreographer_frameCallback64 callback, void* data) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0);
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, 0, CALLBACK_ANIMATION);
 }
 void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
                                                AChoreographer_frameCallback64 callback, void* data,
                                                uint32_t delayMillis) {
     AChoreographer_to_Choreographer(choreographer)
-            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis));
+            ->postFrameCallbackDelayed(nullptr, callback, nullptr, data, ms2ns(delayMillis),
+                                       CALLBACK_ANIMATION);
 }
 void AChoreographer_registerRefreshRateCallback(AChoreographer* choreographer,
                                                 AChoreographer_refreshRateCallback callback,
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index e3be3bc..d0ca78e 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -129,7 +129,7 @@
     std::vector<DisplayConfigImpl> modesPerDisplay[size];
     ui::DisplayConnectionType displayConnectionTypes[size];
     int numModes = 0;
-    for (int i = 0; i < size; ++i) {
+    for (size_t i = 0; i < size; ++i) {
         ui::StaticDisplayInfo staticInfo;
         if (const status_t status =
                     SurfaceComposerClient::getStaticDisplayInfo(ids[i].value, &staticInfo);
@@ -151,7 +151,7 @@
 
         numModes += modes.size();
         modesPerDisplay[i].reserve(modes.size());
-        for (int j = 0; j < modes.size(); ++j) {
+        for (size_t j = 0; j < modes.size(); ++j) {
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
                     DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
@@ -224,7 +224,7 @@
     CHECK_NOT_NULL(display);
     DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
     float maxFps = 0.0;
-    for (int i = 0; i < impl->numConfigs; ++i) {
+    for (size_t i = 0; i < impl->numConfigs; ++i) {
         maxFps = std::max(maxFps, impl->configs[i].fps);
     }
     return maxFps;
@@ -261,7 +261,7 @@
 
     for (size_t i = 0; i < impl->numConfigs; i++) {
         auto* config = impl->configs + i;
-        if (config->id == info.activeDisplayModeId) {
+        if (info.activeDisplayModeId >= 0 && config->id == (size_t)info.activeDisplayModeId) {
             *outConfig = reinterpret_cast<ADisplayConfig*>(config);
             return OK;
         }
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 099f47d..f1453bd 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -98,11 +98,25 @@
      * is created in a detached state, and attachToContext must be called before
      * calls to updateTexImage.
      */
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    SurfaceTexture(uint32_t tex, uint32_t textureTarget, bool useFenceSync, bool isControlledByApp);
+
+    SurfaceTexture(uint32_t textureTarget, bool useFenceSync, bool isControlledByApp);
+
+    SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t textureTarget,
+                   bool useFenceSync, bool isControlledByApp)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+
+    SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t textureTarget, bool useFenceSync,
+                   bool isControlledByApp)
+            __attribute((deprecated("Prefer ctors that create their own surface and consumer.")));
+#else
     SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex, uint32_t textureTarget,
                    bool useFenceSync, bool isControlledByApp);
 
     SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t textureTarget, bool useFenceSync,
                    bool isControlledByApp);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     /**
      * updateTexImage acquires the most recently queued buffer, and sets the
@@ -499,6 +513,8 @@
     friend class EGLConsumer;
 
 private:
+    void initialize();
+
     // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
     // is extending ConsumerBase which also implements FrameAvailableListener.
     class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index 3a09204..ce232cc 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -35,6 +35,49 @@
 
 static const mat4 mtxIdentity;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+SurfaceTexture::SurfaceTexture(uint32_t tex, uint32_t texTarget, bool useFenceSync,
+                               bool isControlledByApp)
+      : ConsumerBase(isControlledByApp),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(tex),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mOpMode(OpMode::attachedToGL) {
+    initialize();
+}
+
+SurfaceTexture::SurfaceTexture(uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
+      : ConsumerBase(isControlledByApp),
+        mCurrentCrop(Rect::EMPTY_RECT),
+        mCurrentTransform(0),
+        mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
+        mCurrentFence(Fence::NO_FENCE),
+        mCurrentTimestamp(0),
+        mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
+        mCurrentFrameNumber(0),
+        mDefaultWidth(1),
+        mDefaultHeight(1),
+        mFilteringEnabled(true),
+        mTexName(0),
+        mUseFenceSync(useFenceSync),
+        mTexTarget(texTarget),
+        mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
+        mOpMode(OpMode::detached) {
+    initialize();
+}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+
 SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
                                uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
       : ConsumerBase(bq, isControlledByApp),
@@ -53,11 +96,7 @@
         mTexTarget(texTarget),
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mOpMode(OpMode::attachedToGL) {
-    SFT_LOGV("SurfaceTexture");
-
-    memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
-
-    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
+    initialize();
 }
 
 SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget,
@@ -78,11 +117,7 @@
         mTexTarget(texTarget),
         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
         mOpMode(OpMode::detached) {
-    SFT_LOGV("SurfaceTexture");
-
-    memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
-
-    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
+    initialize();
 }
 
 status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) {
@@ -531,4 +566,12 @@
 }
 #endif
 
+void SurfaceTexture::initialize() {
+    SFT_LOGV("SurfaceTexture");
+
+    memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
+
+    mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
+}
+
 } // namespace android
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index 5261287..3205c32 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -112,6 +112,10 @@
         static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
                 AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
         "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+        static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::YCBCR_P210) ==
+                AHARDWAREBUFFER_FORMAT_YCbCr_P210,
+        "HAL and AHardwareBuffer pixel format don't match");
 
 static enum AHardwareBufferStatus filterStatus(status_t status) {
     switch (status) {
@@ -196,10 +200,10 @@
         return BAD_VALUE;
     }
 
-    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK |
-                  AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) {
+    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK) ||
+        usage == 0) {
         ALOGE("Invalid usage flags passed to AHardwareBuffer_lock; only "
-                "AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
+              "AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
         return BAD_VALUE;
     }
 
@@ -248,10 +252,10 @@
 
     if (!buffer) return BAD_VALUE;
 
-    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK |
-                  AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) {
+    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK) ||
+        usage == 0) {
         ALOGE("Invalid usage flags passed to AHardwareBuffer_lock; only "
-                "AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
+              "AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
         return BAD_VALUE;
     }
 
@@ -277,10 +281,10 @@
         int32_t fence, const ARect* rect, AHardwareBuffer_Planes* outPlanes) {
     if (!buffer || !outPlanes) return BAD_VALUE;
 
-    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK |
-                  AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK)) {
+    if (usage & ~(AHARDWAREBUFFER_USAGE_CPU_READ_MASK | AHARDWAREBUFFER_USAGE_CPU_WRITE_MASK) ||
+        usage == 0) {
         ALOGE("Invalid usage flags passed to AHardwareBuffer_lock; only "
-                " AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
+              " AHARDWAREBUFFER_USAGE_CPU_* flags are allowed");
         return BAD_VALUE;
     }
 
@@ -300,7 +304,11 @@
       if (result == 0) {
         outPlanes->planeCount = 3;
         outPlanes->planes[0].data = yuvData.y;
-        if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010) {
+        // P010 & P210 are word-aligned 10-bit semiplaner, and YCbCr_422_I is a single interleaved
+        // plane
+        if (format == AHARDWAREBUFFER_FORMAT_YCbCr_P010 ||
+            format == AHARDWAREBUFFER_FORMAT_YCbCr_P210 ||
+            format == AHARDWAREBUFFER_FORMAT_YCbCr_422_I) {
             outPlanes->planes[0].pixelStride = 2;
         } else {
             outPlanes->planes[0].pixelStride = 1;
@@ -722,6 +730,7 @@
         case AHARDWAREBUFFER_FORMAT_YCrCb_420_SP:
         case AHARDWAREBUFFER_FORMAT_YCbCr_422_I:
         case AHARDWAREBUFFER_FORMAT_YCbCr_P010:
+        case AHARDWAREBUFFER_FORMAT_YCbCr_P210:
             return true;
         default:
             return false;
diff --git a/libs/nativewindow/Android.bp b/libs/nativewindow/Android.bp
index 8558074..a8a86ba 100644
--- a/libs/nativewindow/Android.bp
+++ b/libs/nativewindow/Android.bp
@@ -67,9 +67,6 @@
 
     // Android O
     first_version: "26",
-    export_header_libs: [
-        "libnativewindow_ndk_headers",
-    ],
 }
 
 cc_library {
diff --git a/libs/nativewindow/include/android/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index d05ff34..5a78a5c 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -175,6 +175,14 @@
     AHARDWAREBUFFER_FORMAT_YCbCr_P010               = 0x36,
 
     /**
+     * YUV P210 format.
+     * Must have an even width and height. Can be accessed in OpenGL
+     * shaders through an external sampler. Does not support mip-maps
+     * cube-maps or multi-layered textures.
+     */
+    AHARDWAREBUFFER_FORMAT_YCbCr_P210               = 0x3c,
+
+    /**
      * Corresponding formats:
      *   Vulkan: VK_FORMAT_R8_UNORM
      *   OpenGL ES: GR_GL_R8
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index be6623e..6f816bf 100644
--- a/libs/nativewindow/include/android/native_window.h
+++ b/libs/nativewindow/include/android/native_window.h
@@ -366,14 +366,13 @@
  *
  * See ANativeWindow_setFrameRateWithChangeStrategy().
  *
- * Available since API level 34.
+ * Available since API level 31.
  *
  * \param window pointer to an ANativeWindow object.
  *
  * \return 0 for success, -EINVAL if the window value is invalid.
  */
-inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window)
-        __INTRODUCED_IN(__ANDROID_API_U__) {
+inline int32_t ANativeWindow_clearFrameRate(ANativeWindow* window) __INTRODUCED_IN(31) {
     return ANativeWindow_setFrameRateWithChangeStrategy(window, 0,
             ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
             ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index 969a5cf..33c303a 100644
--- a/libs/nativewindow/include/system/window.h
+++ b/libs/nativewindow/include/system/window.h
@@ -41,6 +41,8 @@
 #include <system/graphics.h>
 #include <unistd.h>
 
+#include <vndk/hardware_buffer.h>
+
 // system/window.h is a superset of the vndk and apex apis
 #include <apex/window.h>
 #include <vndk/window.h>
@@ -257,6 +259,7 @@
     NATIVE_WINDOW_SET_QUERY_INTERCEPTOR           = 47,    /* private */
     NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO         = 48,    /* private */
     NATIVE_WINDOW_GET_LAST_QUEUED_BUFFER2         = 49,    /* private */
+    NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS  = 50,
     // clang-format on
 };
 
@@ -1182,6 +1185,26 @@
     return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
 }
 
+/**
+ * native_window_set_buffers_additional_options(..., ExtendableType* additionalOptions, size_t size)
+ * All buffers dequeued after this call will have the additionalOptions specified.
+ *
+ * This must only be called after api_connect, otherwise NO_INIT is returned. The options are
+ * cleared in api_disconnect & api_connect
+ *
+ * If IAllocator is not v2 or newer this method returns INVALID_OPERATION
+ *
+ * \return NO_ERROR on success.
+ * \return NO_INIT if no api is connected
+ * \return INVALID_OPERATION if additional option support is not available
+ */
+static inline int native_window_set_buffers_additional_options(
+        struct ANativeWindow* window, const AHardwareBufferLongOptions* additionalOptions,
+        size_t additionalOptionsSize) {
+    return window->perform(window, NATIVE_WINDOW_SET_BUFFERS_ADDITIONAL_OPTIONS, additionalOptions,
+                           additionalOptionsSize);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Candidates for APEX visibility
 // These functions are planned to be made stable for APEX modules, but have not
diff --git a/libs/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
index 97740db..faab48b 100644
--- a/libs/nativewindow/rust/Android.bp
+++ b/libs/nativewindow/rust/Android.bp
@@ -26,9 +26,12 @@
     source_stem: "bindings",
     bindgen_flags: [
         "--constified-enum-module=AHardwareBuffer_Format",
+        "--bitfield-enum=ADataSpace",
         "--bitfield-enum=AHardwareBuffer_UsageFlags",
 
         "--allowlist-file=.*/nativewindow/include/.*\\.h",
+        "--allowlist-file=.*/include/cutils/.*\\.h",
+        "--allowlist-file=.*/include_outside_system/cutils/.*\\.h",
         "--blocklist-type",
         "AParcel",
         "--raw-line",
@@ -39,6 +42,7 @@
     ],
     shared_libs: [
         "libbinder_ndk",
+        "libcutils",
         "libnativewindow",
     ],
     rustlibs: [
@@ -66,6 +70,7 @@
     srcs: [":libnativewindow_bindgen_internal"],
     shared_libs: [
         "libbinder_ndk",
+        "libcutils",
         "libnativewindow",
     ],
     rustlibs: [
@@ -105,7 +110,9 @@
     name: "libnativewindow_defaults",
     srcs: ["src/lib.rs"],
     rustlibs: [
+        "android.hardware.common-V2-rust",
         "libbinder_rs",
+        "libbitflags",
         "libnativewindow_bindgen",
     ],
 }
diff --git a/libs/nativewindow/rust/src/handle.rs b/libs/nativewindow/rust/src/handle.rs
new file mode 100644
index 0000000..2b08c1b
--- /dev/null
+++ b/libs/nativewindow/rust/src/handle.rs
@@ -0,0 +1,307 @@
+// Copyright (C) 2024 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.
+
+use android_hardware_common::{
+    aidl::android::hardware::common::NativeHandle::NativeHandle as AidlNativeHandle,
+    binder::ParcelFileDescriptor,
+};
+use std::{
+    ffi::c_int,
+    mem::forget,
+    os::fd::{BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
+    ptr::NonNull,
+};
+
+/// Rust wrapper around `native_handle_t`.
+///
+/// This owns the `native_handle_t` and its file descriptors, and will close them and free it when
+/// it is dropped.
+#[derive(Debug)]
+pub struct NativeHandle(NonNull<ffi::native_handle_t>);
+
+impl NativeHandle {
+    /// Creates a new `NativeHandle` with the given file descriptors and integer values.
+    ///
+    /// The `NativeHandle` will take ownership of the file descriptors and close them when it is
+    /// dropped.
+    pub fn new(fds: Vec<OwnedFd>, ints: &[c_int]) -> Option<Self> {
+        let fd_count = fds.len();
+        // SAFETY: native_handle_create doesn't have any safety requirements.
+        let handle = unsafe {
+            ffi::native_handle_create(fd_count.try_into().unwrap(), ints.len().try_into().unwrap())
+        };
+        let handle = NonNull::new(handle)?;
+        for (i, fd) in fds.into_iter().enumerate() {
+            // SAFETY: `handle` must be valid because it was just created, and the array offset is
+            // within the bounds of what we allocated above.
+            unsafe {
+                *(*handle.as_ptr()).data.as_mut_ptr().add(i) = fd.into_raw_fd();
+            }
+        }
+        for (i, value) in ints.iter().enumerate() {
+            // SAFETY: `handle` must be valid because it was just created, and the array offset is
+            // within the bounds of what we allocated above. Note that `data` is uninitialized
+            // until after this so we can't use `slice::from_raw_parts_mut` or similar to create a
+            // reference to it so we use raw pointers arithmetic instead.
+            unsafe {
+                *(*handle.as_ptr()).data.as_mut_ptr().add(fd_count + i) = *value;
+            }
+        }
+        // SAFETY: `handle` must be valid because it was just created.
+        unsafe {
+            ffi::native_handle_set_fdsan_tag(handle.as_ptr());
+        }
+        Some(Self(handle))
+    }
+
+    /// Returns a borrowed view of all the file descriptors in this native handle.
+    pub fn fds(&self) -> Vec<BorrowedFd> {
+        self.data()[..self.fd_count()]
+            .iter()
+            .map(|fd| {
+                // SAFETY: The `native_handle_t` maintains ownership of the file descriptor so it
+                // won't be closed until this `NativeHandle` is destroyed. The `BorrowedFd` will
+                // have a lifetime constrained to that of `&self`, so it can't outlive it.
+                unsafe { BorrowedFd::borrow_raw(*fd) }
+            })
+            .collect()
+    }
+
+    /// Returns the integer values in this native handle.
+    pub fn ints(&self) -> &[c_int] {
+        &self.data()[self.fd_count()..]
+    }
+
+    /// Destroys the `NativeHandle`, taking ownership of the file descriptors it contained.
+    pub fn into_fds(self) -> Vec<OwnedFd> {
+        // Unset FDSan tag since this `native_handle_t` is no longer the owner of the file
+        // descriptors after this function.
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid.
+        unsafe {
+            ffi::native_handle_unset_fdsan_tag(self.as_ref());
+        }
+        let fds = self.data()[..self.fd_count()]
+            .iter()
+            .map(|fd| {
+                // SAFETY: The `native_handle_t` has ownership of the file descriptor, and
+                // after this we destroy it without closing the file descriptor so we can take over
+                // ownership of it.
+                unsafe { OwnedFd::from_raw_fd(*fd) }
+            })
+            .collect();
+
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid, and it won't be accessed
+        // after this because we own it and forget it.
+        unsafe {
+            assert_eq!(ffi::native_handle_delete(self.0.as_ptr()), 0);
+        }
+        // Don't drop self, as that would cause `native_handle_close` to be called and close the
+        // file descriptors.
+        forget(self);
+        fds
+    }
+
+    /// Returns a reference to the underlying `native_handle_t`.
+    fn as_ref(&self) -> &ffi::native_handle_t {
+        // SAFETY: All the ways of creating a `NativeHandle` ensure that the `native_handle_t` is
+        // valid and initialised, and lives as long as the `NativeHandle`. We enforce Rust's
+        // aliasing rules by giving the reference a lifetime matching that of `&self`.
+        unsafe { self.0.as_ref() }
+    }
+
+    /// Returns the number of file descriptors included in the native handle.
+    fn fd_count(&self) -> usize {
+        self.as_ref().numFds.try_into().unwrap()
+    }
+
+    /// Returns the number of integer values included in the native handle.
+    fn int_count(&self) -> usize {
+        self.as_ref().numInts.try_into().unwrap()
+    }
+
+    /// Returns a slice reference for all the used `data` field of the native handle, including both
+    /// file descriptors and integers.
+    fn data(&self) -> &[c_int] {
+        let total_count = self.fd_count() + self.int_count();
+        // SAFETY: The data must have been initialised with this number of elements when the
+        // `NativeHandle` was created.
+        unsafe { self.as_ref().data.as_slice(total_count) }
+    }
+
+    /// Wraps a raw `native_handle_t` pointer, taking ownership of it.
+    ///
+    /// # Safety
+    ///
+    /// `native_handle` must be a valid pointer to a `native_handle_t`, and must not be used
+    ///  anywhere else after calling this method.
+    pub unsafe fn from_raw(native_handle: NonNull<ffi::native_handle_t>) -> Self {
+        Self(native_handle)
+    }
+
+    /// Creates a new `NativeHandle` wrapping a clone of the given `native_handle_t` pointer.
+    ///
+    /// Unlike [`from_raw`](Self::from_raw) this doesn't take ownership of the pointer passed in, so
+    /// the caller remains responsible for closing and freeing it.
+    ///
+    /// # Safety
+    ///
+    /// `native_handle` must be a valid pointer to a `native_handle_t`.
+    pub unsafe fn clone_from_raw(native_handle: NonNull<ffi::native_handle_t>) -> Option<Self> {
+        // SAFETY: The caller promised that `native_handle` was valid.
+        let cloned = unsafe { ffi::native_handle_clone(native_handle.as_ptr()) };
+        NonNull::new(cloned).map(Self)
+    }
+
+    /// Returns a raw pointer to the wrapped `native_handle_t`.
+    ///
+    /// This is only valid as long as this `NativeHandle` exists, so shouldn't be stored. It mustn't
+    /// be closed or deleted.
+    pub fn as_raw(&self) -> NonNull<ffi::native_handle_t> {
+        self.0
+    }
+
+    /// Turns the `NativeHandle` into a raw `native_handle_t`.
+    ///
+    /// The caller takes ownership of the `native_handle_t` and its file descriptors, so is
+    /// responsible for closing and freeing it.
+    pub fn into_raw(self) -> NonNull<ffi::native_handle_t> {
+        let raw = self.0;
+        forget(self);
+        raw
+    }
+}
+
+impl Clone for NativeHandle {
+    fn clone(&self) -> Self {
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid.
+        unsafe { Self::clone_from_raw(self.0) }.expect("native_handle_clone returned null")
+    }
+}
+
+impl Drop for NativeHandle {
+    fn drop(&mut self) {
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid, and it won't be accessed
+        // after this because we own it and are being dropped.
+        unsafe {
+            assert_eq!(ffi::native_handle_close(self.0.as_ptr()), 0);
+            assert_eq!(ffi::native_handle_delete(self.0.as_ptr()), 0);
+        }
+    }
+}
+
+impl From<AidlNativeHandle> for NativeHandle {
+    fn from(aidl_native_handle: AidlNativeHandle) -> Self {
+        let fds = aidl_native_handle.fds.into_iter().map(OwnedFd::from).collect();
+        Self::new(fds, &aidl_native_handle.ints).unwrap()
+    }
+}
+
+impl From<NativeHandle> for AidlNativeHandle {
+    fn from(native_handle: NativeHandle) -> Self {
+        let ints = native_handle.ints().to_owned();
+        let fds = native_handle.into_fds().into_iter().map(ParcelFileDescriptor::new).collect();
+        Self { ints, fds }
+    }
+}
+
+// SAFETY: `NativeHandle` owns the `native_handle_t`, which just contains some integers and file
+// descriptors, which aren't tied to any particular thread.
+unsafe impl Send for NativeHandle {}
+
+// SAFETY: A `NativeHandle` can be used from different threads simultaneously, as is is just
+// integers and file descriptors.
+unsafe impl Sync for NativeHandle {}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn create_empty() {
+        let handle = NativeHandle::new(vec![], &[]).unwrap();
+        assert_eq!(handle.fds().len(), 0);
+        assert_eq!(handle.ints(), &[]);
+    }
+
+    #[test]
+    fn create_with_ints() {
+        let handle = NativeHandle::new(vec![], &[1, 2, 42]).unwrap();
+        assert_eq!(handle.fds().len(), 0);
+        assert_eq!(handle.ints(), &[1, 2, 42]);
+    }
+
+    #[test]
+    fn create_with_fd() {
+        let file = File::open("/dev/null").unwrap();
+        let handle = NativeHandle::new(vec![file.into()], &[]).unwrap();
+        assert_eq!(handle.fds().len(), 1);
+        assert_eq!(handle.ints(), &[]);
+    }
+
+    #[test]
+    fn clone() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let cloned = original.clone();
+        drop(original);
+
+        assert_eq!(cloned.ints(), &[42]);
+        assert_eq!(cloned.fds().len(), 1);
+
+        drop(cloned);
+    }
+
+    #[test]
+    fn to_fds() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let fds = original.into_fds();
+        assert_eq!(fds.len(), 1);
+    }
+
+    #[test]
+    fn to_aidl() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let aidl = AidlNativeHandle::from(original);
+        assert_eq!(&aidl.ints, &[42]);
+        assert_eq!(aidl.fds.len(), 1);
+    }
+
+    #[test]
+    fn to_from_aidl() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let aidl = AidlNativeHandle::from(original);
+        assert_eq!(&aidl.ints, &[42]);
+        assert_eq!(aidl.fds.len(), 1);
+
+        let converted_back = NativeHandle::from(aidl);
+        assert_eq!(converted_back.ints(), &[42]);
+        assert_eq!(converted_back.fds().len(), 1);
+    }
+}
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
index dc3f51f..aeb2603 100644
--- a/libs/nativewindow/rust/src/lib.rs
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -16,10 +16,12 @@
 
 extern crate nativewindow_bindgen as ffi;
 
+mod handle;
 mod surface;
-pub use surface::Surface;
 
 pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+pub use handle::NativeHandle;
+pub use surface::{buffer::Buffer, Surface};
 
 use binder::{
     binder_impl::{BorrowedParcel, UnstructuredParcelable},
@@ -27,10 +29,87 @@
     unstable_api::{status_result, AsNative},
     StatusCode,
 };
-use ffi::{AHardwareBuffer, AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel};
+use ffi::{
+    AHardwareBuffer, AHardwareBuffer_Desc, AHardwareBuffer_Plane, AHardwareBuffer_Planes,
+    AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel, ARect,
+};
+use std::ffi::c_void;
 use std::fmt::{self, Debug, Formatter};
-use std::mem::ManuallyDrop;
-use std::ptr::{self, null_mut, NonNull};
+use std::mem::{forget, ManuallyDrop};
+use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd};
+use std::ptr::{self, null, null_mut, NonNull};
+
+/// Wrapper around a C `AHardwareBuffer_Desc`.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HardwareBufferDescription(AHardwareBuffer_Desc);
+
+impl HardwareBufferDescription {
+    /// Creates a new `HardwareBufferDescription` with the given parameters.
+    pub fn new(
+        width: u32,
+        height: u32,
+        layers: u32,
+        format: AHardwareBuffer_Format::Type,
+        usage: AHardwareBuffer_UsageFlags,
+        stride: u32,
+    ) -> Self {
+        Self(AHardwareBuffer_Desc {
+            width,
+            height,
+            layers,
+            format,
+            usage: usage.0,
+            stride,
+            rfu0: 0,
+            rfu1: 0,
+        })
+    }
+
+    /// Returns the width from the buffer description.
+    pub fn width(&self) -> u32 {
+        self.0.width
+    }
+
+    /// Returns the height from the buffer description.
+    pub fn height(&self) -> u32 {
+        self.0.height
+    }
+
+    /// Returns the number from layers from the buffer description.
+    pub fn layers(&self) -> u32 {
+        self.0.layers
+    }
+
+    /// Returns the format from the buffer description.
+    pub fn format(&self) -> AHardwareBuffer_Format::Type {
+        self.0.format
+    }
+
+    /// Returns the usage bitvector from the buffer description.
+    pub fn usage(&self) -> AHardwareBuffer_UsageFlags {
+        AHardwareBuffer_UsageFlags(self.0.usage)
+    }
+
+    /// Returns the stride from the buffer description.
+    pub fn stride(&self) -> u32 {
+        self.0.stride
+    }
+}
+
+impl Default for HardwareBufferDescription {
+    fn default() -> Self {
+        Self(AHardwareBuffer_Desc {
+            width: 0,
+            height: 0,
+            layers: 0,
+            format: 0,
+            usage: 0,
+            stride: 0,
+            rfu0: 0,
+            rfu1: 0,
+        })
+    }
+}
 
 /// Wrapper around an opaque C `AHardwareBuffer`.
 #[derive(PartialEq, Eq)]
@@ -43,26 +122,9 @@
     /// that the allocation of the given description will never succeed.
     ///
     /// Available since API 29
-    pub fn is_supported(
-        width: u32,
-        height: u32,
-        layers: u32,
-        format: AHardwareBuffer_Format::Type,
-        usage: AHardwareBuffer_UsageFlags,
-        stride: u32,
-    ) -> bool {
-        let buffer_desc = ffi::AHardwareBuffer_Desc {
-            width,
-            height,
-            layers,
-            format,
-            usage: usage.0,
-            stride,
-            rfu0: 0,
-            rfu1: 0,
-        };
-        // SAFETY: *buffer_desc will never be null.
-        let status = unsafe { ffi::AHardwareBuffer_isSupported(&buffer_desc) };
+    pub fn is_supported(buffer_description: &HardwareBufferDescription) -> bool {
+        // SAFETY: The pointer comes from a reference so must be valid.
+        let status = unsafe { ffi::AHardwareBuffer_isSupported(&buffer_description.0) };
 
         status == 1
     }
@@ -74,27 +136,11 @@
     ///
     /// Available since API level 26.
     #[inline]
-    pub fn new(
-        width: u32,
-        height: u32,
-        layers: u32,
-        format: AHardwareBuffer_Format::Type,
-        usage: AHardwareBuffer_UsageFlags,
-    ) -> Option<Self> {
-        let buffer_desc = ffi::AHardwareBuffer_Desc {
-            width,
-            height,
-            layers,
-            format,
-            usage: usage.0,
-            stride: 0,
-            rfu0: 0,
-            rfu1: 0,
-        };
+    pub fn new(buffer_description: &HardwareBufferDescription) -> Option<Self> {
         let mut ptr = ptr::null_mut();
         // SAFETY: The returned pointer is valid until we drop/deallocate it. The function may fail
         // and return a status, but we check it later.
-        let status = unsafe { ffi::AHardwareBuffer_allocate(&buffer_desc, &mut ptr) };
+        let status = unsafe { ffi::AHardwareBuffer_allocate(&buffer_description.0, &mut ptr) };
 
         if status == 0 {
             Some(Self(NonNull::new(ptr).expect("Allocated AHardwareBuffer was null")))
@@ -103,6 +149,50 @@
         }
     }
 
+    /// Creates a `HardwareBuffer` from a native handle.
+    ///
+    /// The native handle is cloned, so this doesn't take ownership of the original handle passed
+    /// in.
+    pub fn create_from_handle(
+        handle: &NativeHandle,
+        buffer_description: &HardwareBufferDescription,
+    ) -> Result<Self, StatusCode> {
+        let mut buffer = ptr::null_mut();
+        // SAFETY: The caller guarantees that `handle` is valid, and the buffer pointer is valid
+        // because it comes from a reference. The method we pass means that
+        // `AHardwareBuffer_createFromHandle` will clone the handle rather than taking ownership of
+        // it.
+        let status = unsafe {
+            ffi::AHardwareBuffer_createFromHandle(
+                &buffer_description.0,
+                handle.as_raw().as_ptr(),
+                ffi::CreateFromHandleMethod_AHARDWAREBUFFER_CREATE_FROM_HANDLE_METHOD_CLONE
+                    .try_into()
+                    .unwrap(),
+                &mut buffer,
+            )
+        };
+        status_result(status)?;
+        Ok(Self(NonNull::new(buffer).expect("Allocated AHardwareBuffer was null")))
+    }
+
+    /// Returns a clone of the native handle of the buffer.
+    ///
+    /// Returns `None` if the operation fails for any reason.
+    pub fn cloned_native_handle(&self) -> Option<NativeHandle> {
+        // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+        // because it must have been allocated by `AHardwareBuffer_allocate`,
+        // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+        // released it.
+        let native_handle = unsafe { ffi::AHardwareBuffer_getNativeHandle(self.0.as_ptr()) };
+        NonNull::new(native_handle.cast_mut()).and_then(|native_handle| {
+            // SAFETY: `AHardwareBuffer_getNativeHandle` should have returned a valid pointer which
+            // is valid at least as long as the buffer is, and `clone_from_raw` clones it rather
+            // than taking ownership of it so the original `native_handle` isn't stored.
+            unsafe { NativeHandle::clone_from_raw(native_handle) }
+        })
+    }
+
     /// Adopts the given raw pointer and wraps it in a Rust HardwareBuffer.
     ///
     /// # Safety
@@ -115,8 +205,8 @@
         Self(buffer_ptr)
     }
 
-    /// Creates a new Rust HardwareBuffer to wrap the given AHardwareBuffer without taking ownership
-    /// of it.
+    /// Creates a new Rust HardwareBuffer to wrap the given `AHardwareBuffer` without taking
+    /// ownership of it.
     ///
     /// Unlike [`from_raw`](Self::from_raw) this method will increment the refcount on the buffer.
     /// This means that the caller can continue to use the raw buffer it passed in, and must call
@@ -132,8 +222,20 @@
         Self(buffer)
     }
 
-    /// Get the internal |AHardwareBuffer| pointer without decrementing the refcount. This can
-    /// be used to provide a pointer to the AHB for a C/C++ API over the FFI.
+    /// Returns the internal `AHardwareBuffer` pointer.
+    ///
+    /// This is only valid as long as this `HardwareBuffer` exists, so shouldn't be stored. It can
+    /// be used to provide a pointer for a C/C++ API over FFI.
+    pub fn as_raw(&self) -> NonNull<AHardwareBuffer> {
+        self.0
+    }
+
+    /// Gets the internal `AHardwareBuffer` pointer without decrementing the refcount. This can
+    /// be used for a C/C++ API which takes ownership of the pointer.
+    ///
+    /// The caller is responsible for releasing the `AHardwareBuffer` pointer by calling
+    /// `AHardwareBuffer_release` when it is finished with it, or may convert it back to a Rust
+    /// `HardwareBuffer` by calling [`HardwareBuffer::from_raw`].
     pub fn into_raw(self) -> NonNull<AHardwareBuffer> {
         let buffer = ManuallyDrop::new(self);
         buffer.0
@@ -155,37 +257,8 @@
         out_id
     }
 
-    /// Get the width of this buffer
-    pub fn width(&self) -> u32 {
-        self.description().width
-    }
-
-    /// Get the height of this buffer
-    pub fn height(&self) -> u32 {
-        self.description().height
-    }
-
-    /// Get the number of layers of this buffer
-    pub fn layers(&self) -> u32 {
-        self.description().layers
-    }
-
-    /// Get the format of this buffer
-    pub fn format(&self) -> AHardwareBuffer_Format::Type {
-        self.description().format
-    }
-
-    /// Get the usage bitvector of this buffer
-    pub fn usage(&self) -> AHardwareBuffer_UsageFlags {
-        AHardwareBuffer_UsageFlags(self.description().usage)
-    }
-
-    /// Get the stride of this buffer
-    pub fn stride(&self) -> u32 {
-        self.description().stride
-    }
-
-    fn description(&self) -> ffi::AHardwareBuffer_Desc {
+    /// Returns the description of this buffer.
+    pub fn description(&self) -> HardwareBufferDescription {
         let mut buffer_desc = ffi::AHardwareBuffer_Desc {
             width: 0,
             height: 0,
@@ -196,9 +269,191 @@
             rfu0: 0,
             rfu1: 0,
         };
-        // SAFETY: neither the buffer nor AHardwareBuffer_Desc pointers will be null.
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the
+        // AHardwareBuffer_Desc pointer is valid because it comes from a reference.
         unsafe { ffi::AHardwareBuffer_describe(self.0.as_ref(), &mut buffer_desc) };
-        buffer_desc
+        HardwareBufferDescription(buffer_desc)
+    }
+
+    /// Locks the hardware buffer for direct CPU access.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    /// - If `rect` is not `None`, the caller must not modify the buffer outside of that rectangle.
+    pub unsafe fn lock<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<HardwareBufferGuard<'a>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut address = null_mut();
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the buffer address out
+        // pointer is valid because it comes from a reference. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lock(self.0.as_ptr(), usage.0, fence, rect, &mut address)
+        };
+        status_result(status)?;
+        Ok(HardwareBufferGuard {
+            buffer: self,
+            address: NonNull::new(address)
+                .expect("AHardwareBuffer_lock set a null outVirtualAddress"),
+        })
+    }
+
+    /// Lock a potentially multi-planar hardware buffer for direct CPU access.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    /// - If `rect` is not `None`, the caller must not modify the buffer outside of that rectangle.
+    pub unsafe fn lock_planes<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<Vec<PlaneGuard<'a>>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut planes = AHardwareBuffer_Planes {
+            planeCount: 0,
+            planes: [const { AHardwareBuffer_Plane { data: null_mut(), pixelStride: 0, rowStride: 0 } };
+                4],
+        };
+
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the various out
+        // pointers are valid because they come from references. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lockPlanes(self.0.as_ptr(), usage.0, fence, rect, &mut planes)
+        };
+        status_result(status)?;
+        let plane_count = planes.planeCount.try_into().unwrap();
+        Ok(planes.planes[..plane_count]
+            .iter()
+            .map(|plane| PlaneGuard {
+                guard: HardwareBufferGuard {
+                    buffer: self,
+                    address: NonNull::new(plane.data)
+                        .expect("AHardwareBuffer_lockAndGetInfo set a null outVirtualAddress"),
+                },
+                pixel_stride: plane.pixelStride,
+                row_stride: plane.rowStride,
+            })
+            .collect())
+    }
+
+    /// Locks the hardware buffer for direct CPU access, returning information about the bytes per
+    /// pixel and stride as well.
+    ///
+    /// # Safety
+    ///
+    /// - If `fence` is `None`, the caller must ensure that all writes to the buffer have completed
+    ///   before calling this function.
+    /// - If the buffer has `AHARDWAREBUFFER_FORMAT_BLOB`, multiple threads or process may lock the
+    ///   buffer simultaneously, but the caller must ensure that they don't access it simultaneously
+    ///   and break Rust's aliasing rules, like any other shared memory.
+    /// - Otherwise if `usage` includes `AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY` or
+    ///   `AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN`, the caller must ensure that no other threads or
+    ///   processes lock the buffer simultaneously for any usage.
+    /// - Otherwise, the caller must ensure that no other threads lock the buffer for writing
+    ///   simultaneously.
+    pub unsafe fn lock_and_get_info<'a>(
+        &'a self,
+        usage: AHardwareBuffer_UsageFlags,
+        fence: Option<BorrowedFd>,
+        rect: Option<&ARect>,
+    ) -> Result<LockedBufferInfo<'a>, StatusCode> {
+        let fence = if let Some(fence) = fence { fence.as_raw_fd() } else { -1 };
+        let rect = rect.map(ptr::from_ref).unwrap_or(null());
+        let mut address = null_mut();
+        let mut bytes_per_pixel = 0;
+        let mut stride = 0;
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid, and the various out
+        // pointers are valid because they come from references. Our caller promises that writes have
+        // completed and there will be no simultaneous read/write locks.
+        let status = unsafe {
+            ffi::AHardwareBuffer_lockAndGetInfo(
+                self.0.as_ptr(),
+                usage.0,
+                fence,
+                rect,
+                &mut address,
+                &mut bytes_per_pixel,
+                &mut stride,
+            )
+        };
+        status_result(status)?;
+        Ok(LockedBufferInfo {
+            guard: HardwareBufferGuard {
+                buffer: self,
+                address: NonNull::new(address)
+                    .expect("AHardwareBuffer_lockAndGetInfo set a null outVirtualAddress"),
+            },
+            bytes_per_pixel: bytes_per_pixel as u32,
+            stride: stride as u32,
+        })
+    }
+
+    /// Unlocks the hardware buffer from direct CPU access.
+    ///
+    /// Must be called after all changes to the buffer are completed by the caller. This will block
+    /// until the unlocking is complete and the buffer contents are updated.
+    fn unlock(&self) -> Result<(), StatusCode> {
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid.
+        let status = unsafe { ffi::AHardwareBuffer_unlock(self.0.as_ptr(), null_mut()) };
+        status_result(status)?;
+        Ok(())
+    }
+
+    /// Unlocks the hardware buffer from direct CPU access.
+    ///
+    /// Must be called after all changes to the buffer are completed by the caller.
+    ///
+    /// This may not block until all work is completed, but rather will return a file descriptor
+    /// which will be signalled once the unlocking is complete and the buffer contents is updated.
+    /// If `Ok(None)` is returned then unlocking has already completed and no further waiting is
+    /// necessary. The file descriptor may be passed to a subsequent call to [`Self::lock`].
+    pub fn unlock_with_fence(
+        &self,
+        guard: HardwareBufferGuard,
+    ) -> Result<Option<OwnedFd>, StatusCode> {
+        // Forget the guard so that its `Drop` implementation doesn't try to unlock the
+        // HardwareBuffer again.
+        forget(guard);
+
+        let mut fence = -2;
+        // SAFETY: The `AHardwareBuffer` pointer we wrap is always valid.
+        let status = unsafe { ffi::AHardwareBuffer_unlock(self.0.as_ptr(), &mut fence) };
+        let fence = if fence < 0 {
+            None
+        } else {
+            // SAFETY: `AHardwareBuffer_unlock` gives us ownership of the fence file descriptor.
+            Some(unsafe { OwnedFd::from_raw_fd(fence) })
+        };
+        status_result(status)?;
+        Ok(fence)
     }
 }
 
@@ -275,25 +530,76 @@
 //     according to the docs on the underlying gralloc calls)
 unsafe impl Sync for HardwareBuffer {}
 
+/// A guard for when a `HardwareBuffer` is locked.
+///
+/// The `HardwareBuffer` will be unlocked when this is dropped, or may be unlocked via
+/// [`HardwareBuffer::unlock_with_fence`].
+#[derive(Debug)]
+pub struct HardwareBufferGuard<'a> {
+    buffer: &'a HardwareBuffer,
+    /// The address of the buffer in memory.
+    pub address: NonNull<c_void>,
+}
+
+impl Drop for HardwareBufferGuard<'_> {
+    fn drop(&mut self) {
+        self.buffer
+            .unlock()
+            .expect("Failed to unlock HardwareBuffer when dropping HardwareBufferGuard");
+    }
+}
+
+/// A guard for when a `HardwareBuffer` is locked, with additional information about the number of
+/// bytes per pixel and stride.
+#[derive(Debug)]
+pub struct LockedBufferInfo<'a> {
+    /// The locked buffer guard.
+    pub guard: HardwareBufferGuard<'a>,
+    /// The number of bytes used for each pixel in the buffer.
+    pub bytes_per_pixel: u32,
+    /// The stride in bytes between rows in the buffer.
+    pub stride: u32,
+}
+
+/// A guard for a single plane of a locked `HardwareBuffer`, with additional information about the
+/// stride.
+#[derive(Debug)]
+pub struct PlaneGuard<'a> {
+    /// The locked buffer guard.
+    pub guard: HardwareBufferGuard<'a>,
+    /// The stride in bytes between the color channel for one pixel to the next pixel.
+    pub pixel_stride: u32,
+    /// The stride in bytes between rows in the buffer.
+    pub row_stride: u32,
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
 
     #[test]
     fn create_valid_buffer_returns_ok() {
-        let buffer = HardwareBuffer::new(
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
             512,
             512,
             1,
             AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-        );
+            0,
+        ));
         assert!(buffer.is_some());
     }
 
     #[test]
     fn create_invalid_buffer_returns_err() {
-        let buffer = HardwareBuffer::new(512, 512, 1, 0, AHardwareBuffer_UsageFlags(0));
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            512,
+            512,
+            1,
+            0,
+            AHardwareBuffer_UsageFlags(0),
+            0,
+        ));
         assert!(buffer.is_none());
     }
 
@@ -319,39 +625,45 @@
         // SAFETY: The pointer must be valid because it was just allocated successfully, and we
         // don't use it after calling this.
         let buffer = unsafe { HardwareBuffer::from_raw(NonNull::new(raw_buffer_ptr).unwrap()) };
-        assert_eq!(buffer.width(), 1024);
+        assert_eq!(buffer.description().width(), 1024);
     }
 
     #[test]
     fn basic_getters() {
-        let buffer = HardwareBuffer::new(
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
             1024,
             512,
             1,
             AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-        )
+            0,
+        ))
         .expect("Buffer with some basic parameters was not created successfully");
 
-        assert_eq!(buffer.width(), 1024);
-        assert_eq!(buffer.height(), 512);
-        assert_eq!(buffer.layers(), 1);
-        assert_eq!(buffer.format(), AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM);
+        let description = buffer.description();
+        assert_eq!(description.width(), 1024);
+        assert_eq!(description.height(), 512);
+        assert_eq!(description.layers(), 1);
         assert_eq!(
-            buffer.usage(),
+            description.format(),
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM
+        );
+        assert_eq!(
+            description.usage(),
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
         );
     }
 
     #[test]
     fn id_getter() {
-        let buffer = HardwareBuffer::new(
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
             1024,
             512,
             1,
             AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-        )
+            0,
+        ))
         .expect("Buffer with some basic parameters was not created successfully");
 
         assert_ne!(0, buffer.id());
@@ -359,13 +671,14 @@
 
     #[test]
     fn clone() {
-        let buffer = HardwareBuffer::new(
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
             1024,
             512,
             1,
             AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-        )
+            0,
+        ))
         .expect("Buffer with some basic parameters was not created successfully");
         let buffer2 = buffer.clone();
 
@@ -374,13 +687,14 @@
 
     #[test]
     fn into_raw() {
-        let buffer = HardwareBuffer::new(
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
             1024,
             512,
             1,
             AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
             AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-        )
+            0,
+        ))
         .expect("Buffer with some basic parameters was not created successfully");
         let buffer2 = buffer.clone();
 
@@ -390,4 +704,130 @@
 
         assert_eq!(remade_buffer, buffer2);
     }
+
+    #[test]
+    fn native_handle_and_back() {
+        let buffer_description = HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            1024,
+        );
+        let buffer = HardwareBuffer::new(&buffer_description)
+            .expect("Buffer with some basic parameters was not created successfully");
+
+        let native_handle =
+            buffer.cloned_native_handle().expect("Failed to get native handle for buffer");
+        let buffer2 = HardwareBuffer::create_from_handle(&native_handle, &buffer_description)
+            .expect("Failed to create buffer from native handle");
+
+        assert_eq!(buffer.description(), buffer_description);
+        assert_eq!(buffer2.description(), buffer_description);
+    }
+
+    #[test]
+    fn lock() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        drop(guard);
+    }
+
+    #[test]
+    fn lock_with_rect() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+        let rect = ARect { left: 10, right: 20, top: 35, bottom: 45 };
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                Some(&rect),
+            )
+        }
+        .unwrap();
+
+        drop(guard);
+    }
+
+    #[test]
+    fn unlock_with_fence() {
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            1024,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let guard = unsafe {
+            buffer.lock(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        buffer.unlock_with_fence(guard).unwrap();
+    }
+
+    #[test]
+    fn lock_with_info() {
+        const WIDTH: u32 = 1024;
+        let buffer = HardwareBuffer::new(&HardwareBufferDescription::new(
+            WIDTH,
+            512,
+            1,
+            AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+            0,
+        ))
+        .expect("Failed to create buffer");
+
+        // SAFETY: No other threads or processes have access to the buffer.
+        let info = unsafe {
+            buffer.lock_and_get_info(
+                AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+                None,
+                None,
+            )
+        }
+        .unwrap();
+
+        assert_eq!(info.bytes_per_pixel, 4);
+        assert_eq!(info.stride, WIDTH * 4);
+        drop(info);
+    }
 }
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
index 25fea80..ed52471 100644
--- a/libs/nativewindow/rust/src/surface.rs
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -14,20 +14,27 @@
 
 //! Rust wrapper for `ANativeWindow` and related types.
 
+pub(crate) mod buffer;
+
 use binder::{
     binder_impl::{BorrowedParcel, UnstructuredParcelable},
     impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
     unstable_api::{status_result, AsNative},
     StatusCode,
 };
+use bitflags::bitflags;
+use buffer::Buffer;
 use nativewindow_bindgen::{
-    AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire, ANativeWindow_getFormat,
-    ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_readFromParcel,
-    ANativeWindow_release, ANativeWindow_writeToParcel,
+    ADataSpace, AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire,
+    ANativeWindow_getBuffersDataSpace, ANativeWindow_getBuffersDefaultDataSpace,
+    ANativeWindow_getFormat, ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_lock,
+    ANativeWindow_readFromParcel, ANativeWindow_release, ANativeWindow_setBuffersDataSpace,
+    ANativeWindow_setBuffersGeometry, ANativeWindow_setBuffersTransform,
+    ANativeWindow_unlockAndPost, ANativeWindow_writeToParcel, ARect,
 };
 use std::error::Error;
 use std::fmt::{self, Debug, Display, Formatter};
-use std::ptr::{null_mut, NonNull};
+use std::ptr::{self, null_mut, NonNull};
 
 /// Wrapper around an opaque C `ANativeWindow`.
 #[derive(PartialEq, Eq)]
@@ -60,6 +67,132 @@
         let format = unsafe { ANativeWindow_getFormat(self.0.as_ptr()) };
         format.try_into().map_err(|_| ErrorCode(format))
     }
+
+    /// Changes the format and size of the window buffers.
+    ///
+    /// The width and height control the number of pixels in the buffers, not the dimensions of the
+    /// window on screen. If these are different than the window's physical size, then its buffer
+    /// will be scaled to match that size when compositing it to the screen. The width and height
+    /// must be either both zero or both non-zero. If both are 0 then the window's base value will
+    /// come back in force.
+    pub fn set_buffers_geometry(
+        &mut self,
+        width: i32,
+        height: i32,
+        format: AHardwareBuffer_Format::Type,
+    ) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe {
+            ANativeWindow_setBuffersGeometry(
+                self.0.as_ptr(),
+                width,
+                height,
+                format.try_into().expect("Invalid format"),
+            )
+        };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Sets a transfom that will be applied to future buffers posted to the window.
+    pub fn set_buffers_transform(&mut self, transform: Transform) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status =
+            unsafe { ANativeWindow_setBuffersTransform(self.0.as_ptr(), transform.bits() as i32) };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Sets the data space that will be applied to future buffers posted to the window.
+    pub fn set_buffers_data_space(&mut self, data_space: ADataSpace) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe { ANativeWindow_setBuffersDataSpace(self.0.as_ptr(), data_space.0) };
+
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
+
+    /// Gets the data space of the buffers in the window.
+    pub fn get_buffers_data_space(&mut self) -> Result<ADataSpace, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let data_space = unsafe { ANativeWindow_getBuffersDataSpace(self.0.as_ptr()) };
+
+        if data_space < 0 {
+            Err(ErrorCode(data_space))
+        } else {
+            Ok(ADataSpace(data_space))
+        }
+    }
+
+    /// Gets the default data space of the buffers in the window as set by the consumer.
+    pub fn get_buffers_default_data_space(&mut self) -> Result<ADataSpace, ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let data_space = unsafe { ANativeWindow_getBuffersDefaultDataSpace(self.0.as_ptr()) };
+
+        if data_space < 0 {
+            Err(ErrorCode(data_space))
+        } else {
+            Ok(ADataSpace(data_space))
+        }
+    }
+
+    /// Locks the window's next drawing surface for writing, and returns it.
+    pub fn lock(&mut self, bounds: Option<&mut ARect>) -> Result<Buffer, ErrorCode> {
+        let mut buffer = buffer::EMPTY;
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it. The other pointers must be valid because the come from
+        // references, and aren't retained after the function returns.
+        let status = unsafe {
+            ANativeWindow_lock(
+                self.0.as_ptr(),
+                &mut buffer,
+                bounds.map(ptr::from_mut).unwrap_or(null_mut()),
+            )
+        };
+        if status != 0 {
+            return Err(ErrorCode(status));
+        }
+
+        Ok(Buffer::new(buffer, self))
+    }
+
+    /// Unlocks the window's drawing surface which was previously locked, posting the new buffer to
+    /// the display.
+    ///
+    /// This shouldn't be called directly but via the [`Buffer`], hence is not public here.
+    fn unlock_and_post(&mut self) -> Result<(), ErrorCode> {
+        // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+        // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+        // and we have not yet released it.
+        let status = unsafe { ANativeWindow_unlockAndPost(self.0.as_ptr()) };
+        if status == 0 {
+            Ok(())
+        } else {
+            Err(ErrorCode(status))
+        }
+    }
 }
 
 impl Drop for Surface {
@@ -141,3 +274,19 @@
         write!(f, "Error {}", self.0)
     }
 }
+
+bitflags! {
+    /// Transforms that can be applied to buffers as they are displayed to a window.
+    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
+    pub struct Transform: u32 {
+        const MIRROR_HORIZONTAL = 0x01;
+        const MIRROR_VERTICAL = 0x02;
+        const ROTATE_90 = 0x04;
+    }
+}
+
+impl Transform {
+    pub const IDENTITY: Self = Self::empty();
+    pub const ROTATE_180: Self = Self::MIRROR_HORIZONTAL.union(Self::MIRROR_VERTICAL);
+    pub const ROTATE_270: Self = Self::ROTATE_180.union(Self::ROTATE_90);
+}
diff --git a/libs/nativewindow/rust/src/surface/buffer.rs b/libs/nativewindow/rust/src/surface/buffer.rs
new file mode 100644
index 0000000..a2d74d4
--- /dev/null
+++ b/libs/nativewindow/rust/src/surface/buffer.rs
@@ -0,0 +1,68 @@
+// Copyright (C) 2024 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.
+
+use super::{ErrorCode, Surface};
+use nativewindow_bindgen::{AHardwareBuffer_Format, ANativeWindow_Buffer};
+use std::ptr::null_mut;
+
+/// An empty `ANativeWindow_Buffer`.
+pub const EMPTY: ANativeWindow_Buffer = ANativeWindow_Buffer {
+    width: 0,
+    height: 0,
+    stride: 0,
+    format: 0,
+    bits: null_mut(),
+    reserved: [0; 6],
+};
+
+/// Rust wrapper for `ANativeWindow_Buffer`, representing a locked buffer from a [`Surface`].
+pub struct Buffer<'a> {
+    /// The wrapped `ANativeWindow_Buffer`.
+    pub buffer: ANativeWindow_Buffer,
+    surface: &'a mut Surface,
+}
+
+impl<'a> Buffer<'a> {
+    pub(crate) fn new(buffer: ANativeWindow_Buffer, surface: &'a mut Surface) -> Self {
+        Self { buffer, surface }
+    }
+
+    /// Unlocks the window's drawing surface which was previously locked to create this buffer,
+    /// posting the buffer to the display.
+    pub fn unlock_and_post(self) -> Result<(), ErrorCode> {
+        self.surface.unlock_and_post()
+    }
+
+    /// The number of pixels that are shown horizontally.
+    pub fn width(&self) -> i32 {
+        self.buffer.width
+    }
+
+    /// The number of pixels that are shown vertically.
+    pub fn height(&self) -> i32 {
+        self.buffer.height
+    }
+
+    /// The number of pixels that a line in the buffer takes in memory.
+    ///
+    /// This may be greater than the width.
+    pub fn stride(&self) -> i32 {
+        self.buffer.stride
+    }
+
+    /// The pixel format of the buffer.
+    pub fn format(&self) -> Result<AHardwareBuffer_Format::Type, ErrorCode> {
+        self.buffer.format.try_into().map_err(|_| ErrorCode(self.buffer.format))
+    }
+}
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
index 5689f7d..5046a80 100644
--- a/libs/nativewindow/rust/sys/nativewindow_bindings.h
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -20,3 +20,5 @@
 #include <android/hdr_metadata.h>
 #include <android/native_window.h>
 #include <android/native_window_aidl.h>
+#include <cutils/native_handle.h>
+#include <vndk/hardware_buffer.h>
diff --git a/libs/nativewindow/tests/ANativeWindowTest.cpp b/libs/nativewindow/tests/ANativeWindowTest.cpp
index 6cf8291..937ff02 100644
--- a/libs/nativewindow/tests/ANativeWindowTest.cpp
+++ b/libs/nativewindow/tests/ANativeWindowTest.cpp
@@ -50,9 +50,14 @@
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGV("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        mItemConsumer = new BufferItemConsumer(GRALLOC_USAGE_SW_READ_OFTEN);
+        mWindow = new TestableSurface(mItemConsumer->getSurface()->getIGraphicBufferProducer());
+#else
         BufferQueue::createBufferQueue(&mProducer, &mConsumer);
         mItemConsumer = new BufferItemConsumer(mConsumer, GRALLOC_USAGE_SW_READ_OFTEN);
         mWindow = new TestableSurface(mProducer);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         const int success = native_window_api_connect(mWindow.get(), NATIVE_WINDOW_API_CPU);
         EXPECT_EQ(0, success);
     }
@@ -64,10 +69,12 @@
         const int success = native_window_api_disconnect(mWindow.get(), NATIVE_WINDOW_API_CPU);
         EXPECT_EQ(0, success);
     }
+
+#if !COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     sp<IGraphicBufferProducer> mProducer;
     sp<IGraphicBufferConsumer> mConsumer;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     sp<BufferItemConsumer> mItemConsumer;
-
     sp<TestableSurface> mWindow;
 };
 
diff --git a/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs b/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs
index 876f6c8..73a7e95 100644
--- a/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs
+++ b/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs
@@ -22,13 +22,14 @@
 
 #[inline]
 fn create_720p_buffer() -> HardwareBuffer {
-    HardwareBuffer::new(
+    HardwareBuffer::new(&HardwareBufferDescription::new(
         1280,
         720,
         1,
         AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
         AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
-    )
+        0,
+    ))
     .unwrap()
 }
 
@@ -51,7 +52,7 @@
     // underlying call to AHardwareBuffer_describe.
     c.bench_with_input(BenchmarkId::new("desc", "buffer"), &buffer, |b, buffer| {
         b.iter(|| {
-            buffer.width();
+            buffer.description().width();
         })
     });
 }
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index b501d40..d248ea0 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -50,6 +50,7 @@
         "libshaders",
         "libtonemap",
         "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -83,19 +84,30 @@
         "skia/AutoBackendTexture.cpp",
         "skia/Cache.cpp",
         "skia/ColorSpaces.cpp",
+        "skia/GaneshVkRenderEngine.cpp",
+        "skia/GraphiteVkRenderEngine.cpp",
         "skia/GLExtensions.cpp",
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
         "skia/SkiaVkRenderEngine.cpp",
+        "skia/VulkanInterface.cpp",
+        "skia/compat/GaneshBackendTexture.cpp",
+        "skia/compat/GaneshGpuContext.cpp",
+        "skia/compat/GraphiteBackendTexture.cpp",
+        "skia/compat/GraphiteGpuContext.cpp",
         "skia/debug/CaptureTimer.cpp",
         "skia/debug/CommonPool.cpp",
         "skia/debug/SkiaCapture.cpp",
         "skia/debug/SkiaMemoryReporter.cpp",
         "skia/filters/BlurFilter.cpp",
+        "skia/filters/GainmapFactory.cpp",
         "skia/filters/GaussianBlurFilter.cpp",
+        "skia/filters/KawaseBlurDualFilter.cpp",
         "skia/filters/KawaseBlurFilter.cpp",
         "skia/filters/LinearEffect.cpp",
+        "skia/filters/MouriMap.cpp",
         "skia/filters/StretchShaderFactory.cpp",
+        "skia/filters/EdgeExtensionShaderFactory.cpp",
     ],
 }
 
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 6f2a96a..8d0fbba 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
+#include <common/trace.h>
 #include <log/log.h>
 #include <renderengine/RenderEngine.h>
 #include <renderengine/impl/ExternalTexture.h>
 #include <ui/GraphicBuffer.h>
-#include <utils/Trace.h>
 
 namespace android::renderengine::impl {
 
@@ -35,7 +35,7 @@
 }
 
 void ExternalTexture::remapBuffer() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     {
         auto buf = mBuffer;
         mRenderEngine.unmapExternalTextureBuffer(std::move(buf));
diff --git a/libs/renderengine/OWNERS b/libs/renderengine/OWNERS
index 66e1aa1..e296283 100644
--- a/libs/renderengine/OWNERS
+++ b/libs/renderengine/OWNERS
@@ -5,4 +5,5 @@
 djsollen@google.com
 jreck@google.com
 lpy@google.com
-scroggo@google.com
+nscobie@google.com
+sallyqi@google.com
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index 233134d..907590a 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -16,40 +16,71 @@
 
 #include <renderengine/RenderEngine.h>
 
+#include "renderengine/ExternalTexture.h"
+#include "skia/GaneshVkRenderEngine.h"
+#include "skia/GraphiteVkRenderEngine.h"
+#include "skia/SkiaGLRenderEngine.h"
+#include "threaded/RenderEngineThreaded.h"
+#include "ui/GraphicTypes.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <cutils/properties.h>
 #include <log/log.h>
-#include "renderengine/ExternalTexture.h"
-#include "threaded/RenderEngineThreaded.h"
 
-#include "skia/SkiaGLRenderEngine.h"
-#include "skia/SkiaVkRenderEngine.h"
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE)
+#define COMPILE_GRAPHITE_RENDERENGINE 1
+#else
+#define COMPILE_GRAPHITE_RENDERENGINE 0
+#endif
 
 namespace android {
 namespace renderengine {
 
 std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
-    if (args.threaded == Threaded::YES) {
-        switch (args.graphicsApi) {
-            case GraphicsApi::GL:
-                ALOGD("Threaded RenderEngine with SkiaGL Backend");
-                return renderengine::threaded::RenderEngineThreaded::create([args]() {
-                    return android::renderengine::skia::SkiaGLRenderEngine::create(args);
-                });
-            case GraphicsApi::VK:
-                ALOGD("Threaded RenderEngine with SkiaVK Backend");
-                return renderengine::threaded::RenderEngineThreaded::create([args]() {
-                    return android::renderengine::skia::SkiaVkRenderEngine::create(args);
-                });
+    threaded::CreateInstanceFactory createInstanceFactory;
+
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
+    const RenderEngine::SkiaBackend actualSkiaBackend = args.skiaBackend;
+#else
+    if (args.skiaBackend == RenderEngine::SkiaBackend::GRAPHITE) {
+        ALOGE("RenderEngine with Graphite Skia backend was requested, but Graphite was not "
+              "included in the build. Falling back to Ganesh (%s)",
+              args.graphicsApi == RenderEngine::GraphicsApi::GL ? "GL" : "Vulkan");
+    }
+    const RenderEngine::SkiaBackend actualSkiaBackend = RenderEngine::SkiaBackend::GANESH;
+#endif
+
+    ALOGD("%sRenderEngine with %s Backend (%s)", args.threaded == Threaded::YES ? "Threaded " : "",
+          args.graphicsApi == GraphicsApi::GL ? "SkiaGL" : "SkiaVK",
+          actualSkiaBackend == SkiaBackend::GANESH ? "Ganesh" : "Graphite");
+
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
+    if (actualSkiaBackend == SkiaBackend::GRAPHITE) {
+        createInstanceFactory = [args]() {
+            return android::renderengine::skia::GraphiteVkRenderEngine::create(args);
+        };
+    } else
+#endif
+    { // GANESH
+        if (args.graphicsApi == GraphicsApi::VK) {
+            createInstanceFactory = [args]() {
+                return android::renderengine::skia::GaneshVkRenderEngine::create(args);
+            };
+        } else { // GL
+            createInstanceFactory = [args]() {
+                return android::renderengine::skia::SkiaGLRenderEngine::create(args);
+            };
         }
     }
 
-    switch (args.graphicsApi) {
-        case GraphicsApi::GL:
-            ALOGD("RenderEngine with SkiaGL Backend");
-            return renderengine::skia::SkiaGLRenderEngine::create(args);
-        case GraphicsApi::VK:
-            ALOGD("RenderEngine with SkiaVK Backend");
-            return renderengine::skia::SkiaVkRenderEngine::create(args);
+    if (args.threaded == Threaded::YES) {
+        return renderengine::threaded::RenderEngineThreaded::create(createInstanceFactory);
+    } else {
+        return createInstanceFactory();
     }
 }
 
@@ -71,17 +102,34 @@
                                                   base::unique_fd&& bufferFence) {
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
-    updateProtectedContext(layers, buffer);
+    updateProtectedContext(layers, {buffer.get()});
     drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
     return resultFuture;
 }
 
+ftl::Future<FenceResult> RenderEngine::drawGainmap(
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+    std::future<FenceResult> resultFuture = resultPromise->get_future();
+    updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
+    drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
+                        std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+    return resultFuture;
+}
+
 void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
-                                          const std::shared_ptr<ExternalTexture>& buffer) {
+                                          vector<const ExternalTexture*> buffers) {
     const bool needsProtectedContext =
-            (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
-            std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
-                const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
+            std::any_of(layers.begin(), layers.end(),
+                        [](const LayerSettings& layer) {
+                            const std::shared_ptr<ExternalTexture>& buffer =
+                                    layer.source.buffer.buffer;
+                            return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+                        }) ||
+            std::any_of(buffers.begin(), buffers.end(), [](const ExternalTexture* buffer) {
                 return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
             });
     useProtectedContext(needsProtectedContext);
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
index e1a6f6a..f84db0b 100644
--- a/libs/renderengine/benchmark/Android.bp
+++ b/libs/renderengine/benchmark/Android.bp
@@ -57,6 +57,7 @@
         "libui",
         "libutils",
         "server_configurable_flags",
+        "libtracing_perfetto",
     ],
 
     data: ["resources/*"],
diff --git a/libs/renderengine/benchmark/AndroidTest.xml b/libs/renderengine/benchmark/AndroidTest.xml
new file mode 100644
index 0000000..3b923cb
--- /dev/null
+++ b/libs/renderengine/benchmark/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for librenderengine_bench.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native-metric" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="librenderengine_bench->/data/local/tmp/librenderengine_bench" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+        <option name="native-benchmark-device-path" value="/data/local/tmp" />
+        <option name="benchmark-module-name" value="librenderengine_bench" />
+        <option name="file-exclusion-filter-regex" value=".*\.config$" />
+        <option name="file-exclusion-filter-regex" value=".*/resources/.*" />
+    </test>
+</configuration>
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index 101f519..a9264b3 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -29,6 +29,16 @@
 using namespace android;
 using namespace android::renderengine;
 
+// To run tests:
+/**
+ * mmm frameworks/native/libs/renderengine/benchmark;\
+ * adb push $OUT/data/benchmarktest/librenderengine_bench/librenderengine_bench
+ *      /data/benchmarktest/librenderengine_bench/librenderengine_bench;\
+ * adb shell /data/benchmarktest/librenderengine_bench/librenderengine_bench
+ *
+ * (64-bit devices: out directory contains benchmarktest64 instead of benchmarktest)
+ */
+
 ///////////////////////////////////////////////////////////////////////////////
 //  Helpers for calling drawLayers
 ///////////////////////////////////////////////////////////////////////////////
@@ -64,14 +74,15 @@
     return std::pair<uint32_t, uint32_t>(width, height);
 }
 
-static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::Threaded threaded,
-                                                        RenderEngine::GraphicsApi graphicsApi) {
+static std::unique_ptr<RenderEngine> createRenderEngine(
+        RenderEngine::Threaded threaded, RenderEngine::GraphicsApi graphicsApi,
+        RenderEngine::BlurAlgorithm blurAlgorithm = RenderEngine::BlurAlgorithm::KAWASE) {
     auto args = RenderEngineCreationArgs::Builder()
                         .setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(true)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(blurAlgorithm)
                         .setContextPriority(RenderEngine::ContextPriority::REALTIME)
                         .setThreaded(threaded)
                         .setGraphicsApi(graphicsApi)
@@ -172,28 +183,67 @@
     }
 }
 
+/**
+ * Return a buffer with the image in the provided path, relative to the executable directory
+ */
+static std::shared_ptr<ExternalTexture> createTexture(RenderEngine& re, const char* relPathImg) {
+    // Initially use cpu access so we can decode into it with AImageDecoder.
+    auto [width, height] = getDisplaySize();
+    auto srcBuffer =
+            allocateBuffer(re, width, height, GRALLOC_USAGE_SW_WRITE_OFTEN, "decoded_source");
+    std::string fileName = base::GetExecutableDirectory().append(relPathImg);
+    renderenginebench::decode(fileName.c_str(), srcBuffer->getBuffer());
+    // Now copy into GPU-only buffer for more realistic timing.
+    srcBuffer = copyBuffer(re, srcBuffer, 0, "source");
+    return srcBuffer;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 //  Benchmarks
 ///////////////////////////////////////////////////////////////////////////////
 
+constexpr char kHomescreenPath[] = "/resources/homescreen.png";
+
+/**
+ * Draw a layer with texture and no additional shaders as a baseline to evaluate a shader's impact
+ * on performance
+ */
 template <class... Args>
-void BM_blur(benchmark::State& benchState, Args&&... args) {
+void BM_homescreen(benchmark::State& benchState, Args&&... args) {
     auto args_tuple = std::make_tuple(std::move(args)...);
     auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)),
                                  static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple)));
 
-    // Initially use cpu access so we can decode into it with AImageDecoder.
     auto [width, height] = getDisplaySize();
-    auto srcBuffer =
-            allocateBuffer(*re, width, height, GRALLOC_USAGE_SW_WRITE_OFTEN, "decoded_source");
-    {
-        std::string srcImage = base::GetExecutableDirectory();
-        srcImage.append("/resources/homescreen.png");
-        renderenginebench::decode(srcImage.c_str(), srcBuffer->getBuffer());
+    auto srcBuffer = createTexture(*re, kHomescreenPath);
 
-        // Now copy into GPU-only buffer for more realistic timing.
-        srcBuffer = copyBuffer(*re, srcBuffer, 0, "source");
-    }
+    const FloatRect layerRect(0, 0, width, height);
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = layerRect,
+                    },
+            .source =
+                    PixelSource{
+                            .buffer =
+                                    Buffer{
+                                            .buffer = srcBuffer,
+                                    },
+                    },
+            .alpha = half(1.0f),
+    };
+    auto layers = std::vector<LayerSettings>{layer};
+    benchDrawLayers(*re, layers, benchState, "homescreen");
+}
+
+template <class... Args>
+void BM_homescreen_blur(benchmark::State& benchState, Args&&... args) {
+    auto args_tuple = std::make_tuple(std::move(args)...);
+    auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)),
+                                 static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple)));
+
+    auto [width, height] = getDisplaySize();
+    auto srcBuffer = createTexture(*re, kHomescreenPath);
 
     const FloatRect layerRect(0, 0, width, height);
     LayerSettings layer{
@@ -221,8 +271,55 @@
     };
 
     auto layers = std::vector<LayerSettings>{layer, blurLayer};
-    benchDrawLayers(*re, layers, benchState, "blurred");
+    benchDrawLayers(*re, layers, benchState, "homescreen_blurred");
 }
 
-BENCHMARK_CAPTURE(BM_blur, SkiaGLThreaded, RenderEngine::Threaded::YES,
+template <class... Args>
+void BM_homescreen_edgeExtension(benchmark::State& benchState, Args&&... args) {
+    auto args_tuple = std::make_tuple(std::move(args)...);
+    auto re = createRenderEngine(static_cast<RenderEngine::Threaded>(std::get<0>(args_tuple)),
+                                 static_cast<RenderEngine::GraphicsApi>(std::get<1>(args_tuple)));
+
+    auto [width, height] = getDisplaySize();
+    auto srcBuffer = createTexture(*re, kHomescreenPath);
+
+    LayerSettings layer{
+            .geometry =
+                    Geometry{
+                            .boundaries = FloatRect(0, 0, width, height),
+                    },
+            .source =
+                    PixelSource{
+                            .buffer =
+                                    Buffer{
+                                            .buffer = srcBuffer,
+                                            // Part of the screen is not covered by the texture but
+                                            // will be filled in by the shader
+                                            .textureTransform =
+                                                    mat4(mat3(),
+                                                         vec3(width * 0.3f, height * 0.3f, 0.0f)),
+                                    },
+                    },
+            .alpha = half(1.0f),
+            .edgeExtensionEffect =
+                    EdgeExtensionEffect(/* left */ true,
+                                        /* right  */ false, /* top */ true, /* bottom */ false),
+    };
+    auto layers = std::vector<LayerSettings>{layer};
+    benchDrawLayers(*re, layers, benchState, "homescreen_edge_extension");
+}
+
+BENCHMARK_CAPTURE(BM_homescreen_blur, gaussian, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL, RenderEngine::BlurAlgorithm::GAUSSIAN);
+
+BENCHMARK_CAPTURE(BM_homescreen_blur, kawase, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL, RenderEngine::BlurAlgorithm::KAWASE);
+
+BENCHMARK_CAPTURE(BM_homescreen_blur, kawase_dual_filter, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL, RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER);
+
+BENCHMARK_CAPTURE(BM_homescreen, SkiaGLThreaded, RenderEngine::Threaded::YES,
+                  RenderEngine::GraphicsApi::GL);
+
+BENCHMARK_CAPTURE(BM_homescreen_edgeExtension, SkiaGLThreaded, RenderEngine::Threaded::YES,
                   RenderEngine::GraphicsApi::GL);
diff --git a/libs/renderengine/include/renderengine/BorderRenderInfo.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h
deleted file mode 100644
index 0ee6661..0000000
--- a/libs/renderengine/include/renderengine/BorderRenderInfo.h
+++ /dev/null
@@ -1,36 +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.
- */
-
-#pragma once
-#include <math/mat4.h>
-#include <ui/Region.h>
-
-namespace android {
-namespace renderengine {
-
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    Region combinedRegion;
-
-    bool operator==(const BorderRenderInfo& rhs) const {
-        return (width == rhs.width && color == rhs.color &&
-                combinedRegion.hasSameRects(rhs.combinedRegion));
-    }
-};
-
-} // namespace renderengine
-} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index 8d7c13c..280ec19 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -22,7 +22,6 @@
 
 #include <math/mat4.h>
 #include <renderengine/PrintMatrix.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/DisplayId.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
@@ -88,7 +87,24 @@
     aidl::android::hardware::graphics::composer3::RenderIntent renderIntent =
             aidl::android::hardware::graphics::composer3::RenderIntent::TONE_MAP_COLORIMETRIC;
 
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
+    // Tonemapping strategy to use for each layer. This is only used for tonemapping HDR source
+    // content
+    enum class TonemapStrategy {
+        // Use a tonemapper defined by libtonemap. This may be OEM-defined as of Android 13, aka
+        // undefined.
+        // This is typically a global tonemapper, designed to match what is on screen.
+        Libtonemap,
+        // Use a local tonemapper. Because local tonemapping uses large intermediate allocations,
+        // this
+        // method is primarily recommended for infrequent rendering that does not need to exactly
+        // match
+        // pixels that are on-screen.
+        Local,
+    };
+    TonemapStrategy tonemapStrategy = TonemapStrategy::Libtonemap;
+
+    // For now, meaningful primarily when the TonemappingStrategy is Local
+    float targetHdrSdrRatio = 1.f;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
@@ -100,8 +116,7 @@
             lhs.deviceHandlesColorTransform == rhs.deviceHandlesColorTransform &&
             lhs.orientation == rhs.orientation &&
             lhs.targetLuminanceNits == rhs.targetLuminanceNits &&
-            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent &&
-            lhs.borderInfoList == rhs.borderInfoList;
+            lhs.dimmingStage == rhs.dimmingStage && lhs.renderIntent == rhs.renderIntent;
 }
 
 static const char* orientation_to_string(uint32_t orientation) {
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 8ac0af4..859ae8b 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -31,6 +31,7 @@
 #include <ui/ShadowSettings.h>
 #include <ui/StretchEffect.h>
 #include <ui/Transform.h>
+#include "ui/EdgeExtensionEffect.h"
 
 #include <iosfwd>
 
@@ -134,6 +135,7 @@
     mat4 blurRegionTransform = mat4();
 
     StretchEffect stretchEffect;
+    EdgeExtensionEffect edgeExtensionEffect;
 
     // Name associated with the layer for debugging purposes.
     std::string name;
@@ -183,7 +185,9 @@
             lhs.skipContentDraw == rhs.skipContentDraw && lhs.shadow == rhs.shadow &&
             lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
             lhs.blurRegionTransform == rhs.blurRegionTransform &&
-            lhs.stretchEffect == rhs.stretchEffect && lhs.whitePointNits == rhs.whitePointNits;
+            lhs.stretchEffect == rhs.stretchEffect &&
+            lhs.edgeExtensionEffect == rhs.edgeExtensionEffect &&
+            lhs.whitePointNits == rhs.whitePointNits;
 }
 
 static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
@@ -254,6 +258,10 @@
     *os << "\n}";
 }
 
+static inline void PrintTo(const EdgeExtensionEffect& effect, ::std::ostream* os) {
+    *os << effect;
+}
+
 static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
     *os << "LayerSettings for '" << settings.name.c_str() << "' {";
     *os << "\n    .geometry = ";
@@ -285,6 +293,10 @@
         *os << "\n    .stretchEffect = ";
         PrintTo(settings.stretchEffect, os);
     }
+
+    if (settings.edgeExtensionEffect.hasEffect()) {
+        *os << "\n    .edgeExtensionEffect = " << settings.edgeExtensionEffect;
+    }
     *os << "\n    .whitePointNits = " << settings.whitePointNits;
     *os << "\n}";
 }
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index de05268..0fd982e 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -50,6 +50,11 @@
 #define PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME "debug.renderengine.capture_filename"
 
 /**
+ * Switches the cross-window background blur algorithm.
+ */
+#define PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM "debug.renderengine.blur_algorithm"
+
+/**
  * Allows recording of Skia drawing commands with systrace.
  */
 #define PROPERTY_SKIA_ATRACE_ENABLED "debug.renderengine.skia_atrace_enabled"
@@ -83,6 +88,22 @@
     PROTECTED = 2,
 };
 
+// Toggles for skipping or enabling priming of particular shaders.
+struct PrimeCacheConfig {
+    bool cacheHolePunchLayer = true;
+    bool cacheSolidLayers = true;
+    bool cacheSolidDimmedLayers = true;
+    bool cacheImageLayers = true;
+    bool cacheImageDimmedLayers = true;
+    bool cacheClippedLayers = true;
+    bool cacheShadowLayers = true;
+    bool cacheEdgeExtension = true;
+    bool cachePIPImageLayers = true;
+    bool cacheTransparentImageDimmedLayers = true;
+    bool cacheClippedDimmedImageLayers = true;
+    bool cacheUltraHDR = true;
+};
+
 class RenderEngine {
 public:
     enum class ContextPriority {
@@ -102,9 +123,38 @@
         VK,
     };
 
+    enum class SkiaBackend {
+        GANESH,
+        GRAPHITE,
+    };
+
+    enum class BlurAlgorithm {
+        NONE,
+        GAUSSIAN,
+        KAWASE,
+        KAWASE_DUAL_FILTER,
+    };
+
     static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
 
-    static bool canSupport(GraphicsApi);
+    // Check if the device supports the given GraphicsApi.
+    //
+    // If called for GraphicsApi::VK then underlying (unprotected) VK resources will be preserved
+    // to optimize subsequent VK initialization, but teardown(GraphicsApi::VK) must be invoked if
+    // the caller subsequently decides to NOT use VK.
+    //
+    // The first call may require significant resource initialization, but subsequent checks are
+    // cached internally.
+    static bool canSupport(GraphicsApi graphicsApi);
+
+    // Teardown any GPU API resources that were previously initialized but are no longer needed.
+    //
+    // Must be called with GraphicsApi::VK if canSupport(GraphicsApi::VK) was previously invoked but
+    // the caller subsequently decided to not use VK.
+    //
+    // This is safe to call if there is nothing to teardown, but NOT safe to call if a RenderEngine
+    // instance exists. The RenderEngine destructor will handle its own teardown logic.
+    static void teardown(GraphicsApi graphicsApi);
 
     virtual ~RenderEngine() = 0;
 
@@ -112,7 +162,7 @@
     // This interface, while still in use until a suitable replacement is built,
     // should be considered deprecated, minus some methods which still may be
     // used to support legacy behavior.
-    virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
+    virtual std::future<void> primeCache(PrimeCacheConfig config) = 0;
 
     // dump the extension strings. always call the base class.
     virtual void dump(std::string& result) = 0;
@@ -159,6 +209,13 @@
                                                 const std::shared_ptr<ExternalTexture>& buffer,
                                                 base::unique_fd&& bufferFence);
 
+    virtual ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
+                                                 base::borrowed_fd&& sdrFence,
+                                                 const std::shared_ptr<ExternalTexture>& hdr,
+                                                 base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                                                 ui::Dataspace dataspace,
+                                                 const std::shared_ptr<ExternalTexture>& gainmap);
+
     // Clean-up method that should be called on the main thread after the
     // drawFence returned by drawLayers fires. This method will free up
     // resources used by the most recently drawn frame. If the frame is still
@@ -236,8 +293,7 @@
 
     // Update protectedContext mode depending on whether or not any layer has a protected buffer.
     void updateProtectedContext(const std::vector<LayerSettings>&,
-                                const std::shared_ptr<ExternalTexture>&);
-
+                                std::vector<const ExternalTexture*>);
     // Attempt to switch RenderEngine into and out of protectedContext mode
     virtual void useProtectedContext(bool useProtectedContext) = 0;
 
@@ -245,6 +301,13 @@
             const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
             const DisplaySettings& display, const std::vector<LayerSettings>& layers,
             const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
+
+    virtual void drawGainmapInternal(
+            const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+            const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace,
+            const std::shared_ptr<ExternalTexture>& gainmap) = 0;
 };
 
 struct RenderEngineCreationArgs {
@@ -253,10 +316,11 @@
     bool useColorManagement;
     bool enableProtectedContext;
     bool precacheToneMapperShaderOnly;
-    bool supportsBackgroundBlur;
+    RenderEngine::BlurAlgorithm blurAlgorithm;
     RenderEngine::ContextPriority contextPriority;
     RenderEngine::Threaded threaded;
     RenderEngine::GraphicsApi graphicsApi;
+    RenderEngine::SkiaBackend skiaBackend;
 
     struct Builder;
 
@@ -264,18 +328,20 @@
     // must be created by Builder via constructor with full argument list
     RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
                              bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
-                             bool _supportsBackgroundBlur,
+                             RenderEngine::BlurAlgorithm _blurAlgorithm,
                              RenderEngine::ContextPriority _contextPriority,
                              RenderEngine::Threaded _threaded,
-                             RenderEngine::GraphicsApi _graphicsApi)
+                             RenderEngine::GraphicsApi _graphicsApi,
+                             RenderEngine::SkiaBackend _skiaBackend)
           : pixelFormat(_pixelFormat),
             imageCacheSize(_imageCacheSize),
             enableProtectedContext(_enableProtectedContext),
             precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
-            supportsBackgroundBlur(_supportsBackgroundBlur),
+            blurAlgorithm(_blurAlgorithm),
             contextPriority(_contextPriority),
             threaded(_threaded),
-            graphicsApi(_graphicsApi) {}
+            graphicsApi(_graphicsApi),
+            skiaBackend(_skiaBackend) {}
     RenderEngineCreationArgs() = delete;
 };
 
@@ -298,8 +364,8 @@
         this->precacheToneMapperShaderOnly = precacheToneMapperShaderOnly;
         return *this;
     }
-    Builder& setSupportsBackgroundBlur(bool supportsBackgroundBlur) {
-        this->supportsBackgroundBlur = supportsBackgroundBlur;
+    Builder& setBlurAlgorithm(RenderEngine::BlurAlgorithm blurAlgorithm) {
+        this->blurAlgorithm = blurAlgorithm;
         return *this;
     }
     Builder& setContextPriority(RenderEngine::ContextPriority contextPriority) {
@@ -314,10 +380,14 @@
         this->graphicsApi = graphicsApi;
         return *this;
     }
+    Builder& setSkiaBackend(RenderEngine::SkiaBackend skiaBackend) {
+        this->skiaBackend = skiaBackend;
+        return *this;
+    }
     RenderEngineCreationArgs build() const {
         return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
-                                        precacheToneMapperShaderOnly, supportsBackgroundBlur,
-                                        contextPriority, threaded, graphicsApi);
+                                        precacheToneMapperShaderOnly, blurAlgorithm,
+                                        contextPriority, threaded, graphicsApi, skiaBackend);
     }
 
 private:
@@ -326,10 +396,11 @@
     uint32_t imageCacheSize = 0;
     bool enableProtectedContext = false;
     bool precacheToneMapperShaderOnly = false;
-    bool supportsBackgroundBlur = false;
+    RenderEngine::BlurAlgorithm blurAlgorithm = RenderEngine::BlurAlgorithm::NONE;
     RenderEngine::ContextPriority contextPriority = RenderEngine::ContextPriority::MEDIUM;
     RenderEngine::Threaded threaded = RenderEngine::Threaded::YES;
     RenderEngine::GraphicsApi graphicsApi = RenderEngine::GraphicsApi::GL;
+    RenderEngine::SkiaBackend skiaBackend = RenderEngine::SkiaBackend::GANESH;
 };
 
 } // namespace renderengine
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index a58a65c..fb8331d 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -33,7 +33,7 @@
     RenderEngine();
     ~RenderEngine() override;
 
-    MOCK_METHOD1(primeCache, std::future<void>(bool));
+    MOCK_METHOD1(primeCache, std::future<void>(PrimeCacheConfig));
     MOCK_METHOD1(dump, void(std::string&));
     MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
     MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
@@ -46,6 +46,17 @@
                  ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
                                           const std::shared_ptr<ExternalTexture>&,
                                           base::unique_fd&&));
+    MOCK_METHOD7(drawGainmap,
+                 ftl::Future<FenceResult>(const std::shared_ptr<ExternalTexture>&,
+                                          base::borrowed_fd&&,
+                                          const std::shared_ptr<ExternalTexture>&,
+                                          base::borrowed_fd&&, float, ui::Dataspace,
+                                          const std::shared_ptr<ExternalTexture>&));
+    MOCK_METHOD8(drawGainmapInternal,
+                 void(const std::shared_ptr<std::promise<FenceResult>>&&,
+                      const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&,
+                      const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&, float,
+                      ui::Dataspace, const std::shared_ptr<ExternalTexture>&));
     MOCK_METHOD5(drawLayersInternal,
                  void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
                       const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index ee95e59..b7b7a4d 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,81 +20,21 @@
 #define LOG_TAG "RenderEngine"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <SkImage.h>
-#include <include/gpu/ganesh/SkImageGanesh.h>
-#include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
-#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
-#include <include/gpu/vk/GrVkTypes.h>
-#include <android/hardware_buffer.h>
-#include "ColorSpaces.h"
-#include "log/log_main.h"
-#include "utils/Trace.h"
+#include <include/core/SkImage.h>
+#include <include/core/SkSurface.h>
+
+#include "compat/SkiaBackendTexture.h"
+
+#include <common/trace.h>
+#include <log/log_main.h>
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
-AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer,
-                                       bool isOutputBuffer, CleanupManager& cleanupMgr)
-      : mCleanupMgr(cleanupMgr), mIsOutputBuffer(isOutputBuffer) {
-    ATRACE_CALL();
-    AHardwareBuffer_Desc desc;
-    AHardwareBuffer_describe(buffer, &desc);
-    bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat;
-
-    GrBackendApi backend = context->backend();
-    if (backend == GrBackendApi::kOpenGL) {
-        backendFormat =
-                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
-        mBackendTexture =
-                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
-                                                             buffer,
-                                                             desc.width,
-                                                             desc.height,
-                                                             &mDeleteProc,
-                                                             &mUpdateProc,
-                                                             &mImageCtx,
-                                                             createProtectedImage,
-                                                             backendFormat,
-                                                             isOutputBuffer);
-    } else if (backend == GrBackendApi::kVulkan) {
-        backendFormat =
-                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
-                                                               buffer,
-                                                               desc.format,
-                                                               false);
-        mBackendTexture =
-                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
-                                                                 buffer,
-                                                                 desc.width,
-                                                                 desc.height,
-                                                                 &mDeleteProc,
-                                                                 &mUpdateProc,
-                                                                 &mImageCtx,
-                                                                 createProtectedImage,
-                                                                 backendFormat,
-                                                                 isOutputBuffer);
-    } else {
-        LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
-    }
-
-    mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
-    if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
-        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
-                         "isWriteable:%d format:%d",
-                         this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
-                         desc.format);
-    }
-}
-
-AutoBackendTexture::~AutoBackendTexture() {
-    if (mBackendTexture.isValid()) {
-        mDeleteProc(mImageCtx);
-        mBackendTexture = {};
-    }
-}
+AutoBackendTexture::AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
+                                       CleanupManager& cleanupMgr)
+      : mCleanupMgr(cleanupMgr), mBackendTexture(std::move(backendTexture)) {}
 
 void AutoBackendTexture::unref(bool releaseLocalResources) {
     if (releaseLocalResources) {
@@ -122,95 +62,32 @@
     textureRelease->unref(false);
 }
 
-void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace,
-                     SkColorType colorType) {
-    switch (tex.backend()) {
-        case GrBackendApi::kOpenGL: {
-            GrGLTextureInfo textureInfo;
-            bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &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(), static_cast<int32_t>(dataspace), tex.width(),
-                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
-                             static_cast<int>(tex.textureType()), retrievedTextureInfo,
-                             textureInfo.fTarget, textureInfo.fFormat, colorType);
-            break;
-        }
-        case GrBackendApi::kVulkan: {
-            GrVkImageInfo imageInfo;
-            bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
-            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
-                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
-                             "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
-                             "fSampleCount: %u fLevelCount: %u colorType %i",
-                             msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
-                             tex.height(), tex.hasMipmaps(), tex.isProtected(),
-                             static_cast<int>(tex.textureType()), retrievedImageInfo,
-                             imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
-                             colorType);
-            break;
-        }
-        default:
-            LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, static_cast<unsigned>(tex.backend()));
-            break;
-    }
-}
+sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
+    SFTRACE_CALL();
 
-sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                                             GrDirectContext* context) {
-    ATRACE_CALL();
-
-    if (mBackendTexture.isValid()) {
-        mUpdateProc(mImageCtx, context);
-    }
-
-    auto colorType = mColorType;
-    if (alphaType == kOpaque_SkAlphaType) {
-        if (colorType == kRGBA_8888_SkColorType) {
-            colorType = kRGB_888x_SkColorType;
-        }
-    }
-
-    sk_sp<SkImage> image =
-            SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
-                                        colorType, alphaType, toSkColorSpace(dataspace),
-                                        releaseImageProc, this);
-    if (image.get()) {
-        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
-        ref();
-    }
+    sk_sp<SkImage> image = mBackendTexture->makeImage(alphaType, dataspace, releaseImageProc, this);
+    // The following ref will be counteracted by releaseProc, when SkImage is discarded.
+    ref();
 
     mImage = image;
     mDataspace = dataspace;
-    if (!mImage) {
-        logFatalTexture("Unable to generate SkImage.", mBackendTexture, dataspace, colorType);
-    }
     return mImage;
 }
 
-sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
-                                                        GrDirectContext* context) {
-    ATRACE_CALL();
-    LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
+sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace) {
+    SFTRACE_CALL();
+    LOG_ALWAYS_FATAL_IF(!mBackendTexture->isOutputBuffer(),
+                        "You can't generate an SkSurface for a read-only texture");
     if (!mSurface.get() || mDataspace != dataspace) {
         sk_sp<SkSurface> surface =
-                SkSurfaces::WrapBackendTexture(context, mBackendTexture,
-                                               kTopLeft_GrSurfaceOrigin, 0, mColorType,
-                                               toSkColorSpace(dataspace), nullptr,
-                                               releaseSurfaceProc, this);
-        if (surface.get()) {
-            // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
-            ref();
-        }
+                mBackendTexture->makeSurface(dataspace, releaseSurfaceProc, this);
+        // The following ref will be counteracted by releaseProc, when SkSurface is discarded.
+        ref();
+
         mSurface = surface;
     }
 
     mDataspace = dataspace;
-    if (!mSurface) {
-        logFatalTexture("Unable to generate SkSurface.", mBackendTexture, dataspace, mColorType);
-    }
     return mSurface;
 }
 
diff --git a/libs/renderengine/skia/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 509ac40..a570ad0 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -16,16 +16,16 @@
 
 #pragma once
 
-#include <GrAHardwareBufferUtils.h>
-#include <GrDirectContext.h>
 #include <SkImage.h>
 #include <SkSurface.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 #include <sys/types.h>
 #include <ui/GraphicTypes.h>
 
 #include "android-base/macros.h"
+#include "compat/SkiaBackendTexture.h"
 
-#include <mutex>
+#include <memory>
 #include <vector>
 
 namespace android {
@@ -80,9 +80,8 @@
     // of shared ownership with Skia objects, so we wrap it here instead.
     class LocalRef {
     public:
-        LocalRef(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
-                 CleanupManager& cleanupMgr) {
-            mTexture = new AutoBackendTexture(context, buffer, isOutputBuffer, cleanupMgr);
+        LocalRef(std::unique_ptr<SkiaBackendTexture> backendTexture, CleanupManager& cleanupMgr) {
+            mTexture = new AutoBackendTexture(std::move(backendTexture), cleanupMgr);
             mTexture->ref();
         }
 
@@ -95,17 +94,16 @@
         // Makes a new SkImage from the texture content.
         // As SkImages are immutable but buffer content is not, we create
         // a new SkImage every time.
-        sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                                 GrDirectContext* context) {
-            return mTexture->makeImage(dataspace, alphaType, context);
+        sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType) {
+            return mTexture->makeImage(dataspace, alphaType);
         }
 
         // Makes a new SkSurface from the texture content, if needed.
-        sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context) {
-            return mTexture->getOrCreateSurface(dataspace, context);
+        sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace) {
+            return mTexture->getOrCreateSurface(dataspace);
         }
 
-        SkColorType colorType() const { return mTexture->mColorType; }
+        SkColorType colorType() const { return mTexture->mBackendTexture->internalColorType(); }
 
         DISALLOW_COPY_AND_ASSIGN(LocalRef);
 
@@ -114,12 +112,15 @@
     };
 
 private:
-    // Creates a GrBackendTexture whose contents come from the provided buffer.
-    AutoBackendTexture(GrDirectContext* context, AHardwareBuffer* buffer, bool isOutputBuffer,
+    DISALLOW_COPY_AND_ASSIGN(AutoBackendTexture);
+
+    // Creates an AutoBackendTexture to manage the lifecycle of a given SkiaBackendTexture, which is
+    // in turn backed by an underlying backend-specific texture type.
+    AutoBackendTexture(std::unique_ptr<SkiaBackendTexture> backendTexture,
                        CleanupManager& cleanupMgr);
 
     // The only way to invoke dtor is with unref, when mUsageCount is 0.
-    ~AutoBackendTexture();
+    ~AutoBackendTexture() = default;
 
     void ref() { mUsageCount++; }
 
@@ -130,29 +131,21 @@
     // Makes a new SkImage from the texture content.
     // As SkImages are immutable but buffer content is not, we create
     // a new SkImage every time.
-    sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
-                             GrDirectContext* context);
+    sk_sp<SkImage> makeImage(ui::Dataspace dataspace, SkAlphaType alphaType);
 
     // Makes a new SkSurface from the texture content, if needed.
-    sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace, GrDirectContext* context);
-
-    GrBackendTexture mBackendTexture;
-    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
-    GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
-    GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+    sk_sp<SkSurface> getOrCreateSurface(ui::Dataspace dataspace);
 
     CleanupManager& mCleanupMgr;
 
     static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
     static void releaseImageProc(SkImages::ReleaseContext releaseContext);
 
+    std::unique_ptr<SkiaBackendTexture> mBackendTexture;
     int mUsageCount = 0;
-
-    const bool mIsOutputBuffer;
     sk_sp<SkImage> mImage = nullptr;
     sk_sp<SkSurface> mSurface = nullptr;
     ui::Dataspace mDataspace = ui::Dataspace::UNKNOWN;
-    SkColorType mColorType = kUnknown_SkColorType;
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 7b3b176..3b0f036 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -27,6 +27,8 @@
 #include "ui/Rect.h"
 #include "utils/Timers.h"
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android::renderengine::skia {
 
 namespace {
@@ -335,17 +337,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {0.f, 0.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 1.f,
             .sourceDataspace = kDestDataSpace,
     };
@@ -368,16 +370,16 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = mat4(),
                             .boundaries = rect,
+                            .positionTransform = mat4(),
                             .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = false,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .sourceDataspace = kDestDataSpace,
     };
@@ -419,17 +421,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = mat4(),
                             .boundaries = boundary,
-                            .roundedCornersCrop = rect,
+                            .positionTransform = mat4(),
                             .roundedCornersRadius = {27.f, 27.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = false,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .alpha = 1.f,
             .sourceDataspace = kDestDataSpace,
@@ -487,17 +489,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {0.f, 0.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 1.f,
             .sourceDataspace = kBT2020DataSpace,
     };
@@ -525,17 +527,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
-                            .positionTransform = kScaleAsymmetric,
                             .boundaries = boundary,
-                            .roundedCornersCrop = rect,
+                            .positionTransform = kScaleAsymmetric,
                             .roundedCornersRadius = {64.1f, 64.1f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer =
                                           Buffer{
                                                   .buffer = srcTexture,
-                                                  .maxLuminanceNits = 1000.f,
                                                   .usePremultipliedAlpha = true,
                                                   .isOpaque = true,
+                                                  .maxLuminanceNits = 1000.f,
                                           }},
             .alpha = 0.5f,
             .sourceDataspace = kBT2020DataSpace,
@@ -554,17 +556,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = true}},
+                                                   .isOpaque = true,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 0.5f,
             .sourceDataspace = kExtendedHdrDataSpce,
     };
@@ -592,17 +594,17 @@
     LayerSettings layer{
             .geometry =
                     Geometry{
+                            .boundaries = rect,
                             // The position transform doesn't matter when the reduced shader mode
                             // in in effect. A matrix transform stage is always included.
                             .positionTransform = mat4(),
-                            .boundaries = rect,
-                            .roundedCornersCrop = rect,
                             .roundedCornersRadius = {50.f, 50.f},
+                            .roundedCornersCrop = rect,
                     },
             .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
-                                                   .maxLuminanceNits = 1000.f,
                                                    .usePremultipliedAlpha = true,
-                                                   .isOpaque = false}},
+                                                   .isOpaque = false,
+                                                   .maxLuminanceNits = 1000.f}},
             .alpha = 0.5f,
             .sourceDataspace = kOtherDataSpace,
     };
@@ -619,6 +621,32 @@
     }
 }
 
+static void drawEdgeExtensionLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                    const std::shared_ptr<ExternalTexture>& dstTexture,
+                                    const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    // Make the layer
+    LayerSettings layer{
+            // Make the layer bigger than the texture
+            .geometry = Geometry{.boundaries = FloatRect(0, 0, displayRect.width(),
+                                                         displayRect.height())},
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .isOpaque = 1,
+                                          }},
+            // The type of effect does not affect the shader's uniforms, but the layer must have a
+            // valid EdgeExtensionEffect to apply the shader
+            .edgeExtensionEffect =
+                    EdgeExtensionEffect(true /* left */, false, false, true /* bottom */),
+    };
+    for (float alpha : {0.5, 0.0, 1.0}) {
+        layer.alpha = alpha;
+        auto layers = std::vector<LayerSettings>{layer};
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+
 //
 // The collection of shaders cached here were found by using perfetto to record shader compiles
 // during actions that involve RenderEngine, logging the layer settings, and the shader code
@@ -630,7 +658,7 @@
 //    kFlushAfterEveryLayer = true
 // in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
 //    gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, PrimeCacheConfig config) {
     const int previousCount = renderengine->reportShadersCompiled();
     if (previousCount) {
         ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -694,13 +722,24 @@
                 impl::ExternalTexture>(srcBuffer, *renderengine,
                                        impl::ExternalTexture::Usage::READABLE |
                                                impl::ExternalTexture::Usage::WRITEABLE);
-        drawHolePunchLayer(renderengine, display, dstTexture);
-        drawSolidLayers(renderengine, display, dstTexture);
-        drawSolidLayers(renderengine, p3Display, dstTexture);
-        drawSolidDimmedLayers(renderengine, display, dstTexture);
 
-        drawShadowLayers(renderengine, display, srcTexture);
-        drawShadowLayers(renderengine, p3Display, srcTexture);
+        if (config.cacheHolePunchLayer) {
+            drawHolePunchLayer(renderengine, display, dstTexture);
+        }
+
+        if (config.cacheSolidLayers) {
+            drawSolidLayers(renderengine, display, dstTexture);
+            drawSolidLayers(renderengine, p3Display, dstTexture);
+        }
+
+        if (config.cacheSolidDimmedLayers) {
+            drawSolidDimmedLayers(renderengine, display, dstTexture);
+        }
+
+        if (config.cacheShadowLayers) {
+            drawShadowLayers(renderengine, display, srcTexture);
+            drawShadowLayers(renderengine, p3Display, srcTexture);
+        }
 
         if (renderengine->supportsBackgroundBlur()) {
             drawBlurLayers(renderengine, display, dstTexture);
@@ -736,27 +775,46 @@
         }
 
         for (auto texture : textures) {
-            drawImageLayers(renderengine, display, dstTexture, texture);
+            if (config.cacheImageLayers) {
+                drawImageLayers(renderengine, display, dstTexture, texture);
+            }
 
-            drawImageDimmedLayers(renderengine, display, dstTexture, texture);
-            drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
-            drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+            if (config.cacheImageDimmedLayers) {
+                drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+                drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+                drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+            }
 
-            // Draw layers for b/185569240.
-            drawClippedLayers(renderengine, display, dstTexture, texture);
+            if (config.cacheClippedLayers) {
+                // Draw layers for b/185569240.
+                drawClippedLayers(renderengine, display, dstTexture, texture);
+            }
+
+            if (com::android::graphics::libgui::flags::edge_extension_shader() &&
+                config.cacheEdgeExtension) {
+                drawEdgeExtensionLayers(renderengine, display, dstTexture, texture);
+                drawEdgeExtensionLayers(renderengine, p3Display, dstTexture, texture);
+            }
         }
 
-        drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+        if (config.cachePIPImageLayers) {
+            drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+        }
 
-        drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
-        drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
-                                         externalTexture);
+        if (config.cacheTransparentImageDimmedLayers) {
+            drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture,
+                                             externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+            drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+                                             externalTexture);
+        }
 
-        drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        if (config.cacheClippedDimmedImageLayers) {
+            drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+        }
 
-        if (shouldPrimeUltraHDR) {
+        if (config.cacheUltraHDR) {
             drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
 
             drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 62f6705..259432f 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -16,16 +16,21 @@
 
 #pragma once
 
-namespace android::renderengine::skia {
+namespace android::renderengine {
+
+struct PrimeCacheConfig;
+
+namespace skia {
 
 class SkiaRenderEngine;
 
 class Cache {
 public:
-    static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
+    static void primeShaderCache(SkiaRenderEngine*, PrimeCacheConfig config);
 
 private:
     Cache() = default;
 };
 
-} // namespace android::renderengine::skia
+} // namespace skia
+} // namespace android::renderengine
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.cpp b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
new file mode 100644
index 0000000..a3a43e2
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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 "GaneshVkRenderEngine.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
+
+#include <common/trace.h>
+#include <log/log_main.h>
+#include <sync/sync.h>
+
+namespace android::renderengine::skia {
+
+std::unique_ptr<GaneshVkRenderEngine> GaneshVkRenderEngine::create(
+        const RenderEngineCreationArgs& args) {
+    std::unique_ptr<GaneshVkRenderEngine> engine(new GaneshVkRenderEngine(args));
+    engine->ensureContextsCreated();
+
+    if (getVulkanInterface(false).isInitialized()) {
+        ALOGD("GaneshVkRenderEngine::%s: successfully initialized GaneshVkRenderEngine", __func__);
+        return engine;
+    } else {
+        ALOGE("GaneshVkRenderEngine::%s: could not create GaneshVkRenderEngine. "
+              "Likely insufficient Vulkan support",
+              __func__);
+        return {};
+    }
+}
+
+// Ganesh-specific function signature for fFinishedProc callback.
+static void unref_semaphore(void* semaphore) {
+    SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+            reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+    info->unref();
+}
+
+std::unique_ptr<SkiaGpuContext> GaneshVkRenderEngine::createContext(
+        VulkanInterface& vulkanInterface) {
+    return SkiaGpuContext::MakeVulkan_Ganesh(vulkanInterface.getGaneshBackendContext(),
+                                             mSkSLCacheMonitor);
+}
+
+void GaneshVkRenderEngine::waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) {
+    if (fenceFd.get() < 0) return;
+
+    const int dupedFd = dup(fenceFd.get());
+    if (dupedFd < 0) {
+        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+        sync_wait(fenceFd.get(), -1);
+        return;
+    }
+
+    base::unique_fd fenceDup(dupedFd);
+    VkSemaphore waitSemaphore =
+            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+    GrBackendSemaphore beSemaphore = GrBackendSemaphores::MakeVk(waitSemaphore);
+    constexpr bool kDeleteAfterWait = true;
+    context->grDirectContext()->wait(1, &beSemaphore, kDeleteAfterWait);
+}
+
+base::unique_fd GaneshVkRenderEngine::flushAndSubmit(SkiaGpuContext* context,
+                                                     sk_sp<SkSurface> dstSurface) {
+    sk_sp<GrDirectContext> grContext = context->grDirectContext();
+    {
+        SFTRACE_NAME("flush surface");
+        // TODO: Investigate feasibility of combining this "surface flush" into the "context flush"
+        // below.
+        context->grDirectContext()->flush(dstSurface.get());
+    }
+
+    VulkanInterface& vi = getVulkanInterface(isProtected());
+    VkSemaphore semaphore = vi.createExportableSemaphore();
+    GrBackendSemaphore backendSemaphore = GrBackendSemaphores::MakeVk(semaphore);
+
+    GrFlushInfo flushInfo;
+    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+    if (semaphore != VK_NULL_HANDLE) {
+        destroySemaphoreInfo = new DestroySemaphoreInfo(vi, semaphore);
+        flushInfo.fNumSemaphores = 1;
+        flushInfo.fSignalSemaphores = &backendSemaphore;
+        flushInfo.fFinishedProc = unref_semaphore;
+        flushInfo.fFinishedContext = destroySemaphoreInfo;
+    }
+    GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
+    grContext->submit(GrSyncCpu::kNo);
+    int drawFenceFd = -1;
+    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;
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GaneshVkRenderEngine.h b/libs/renderengine/skia/GaneshVkRenderEngine.h
new file mode 100644
index 0000000..e6123c2
--- /dev/null
+++ b/libs/renderengine/skia/GaneshVkRenderEngine.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2024 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 "skia/SkiaVkRenderEngine.h"
+
+namespace android::renderengine::skia {
+
+class GaneshVkRenderEngine : public SkiaVkRenderEngine {
+public:
+    static std::unique_ptr<GaneshVkRenderEngine> create(const RenderEngineCreationArgs& args);
+
+protected:
+    std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+
+private:
+    GaneshVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.cpp b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
new file mode 100644
index 0000000..390ad6e
--- /dev/null
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2024 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 "GraphiteVkRenderEngine.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/graphite/BackendSemaphore.h>
+#include <include/gpu/graphite/Context.h>
+#include <include/gpu/graphite/Recording.h>
+#include <include/gpu/graphite/vk/VulkanGraphiteTypes.h>
+
+#include <log/log_main.h>
+#include <sync/sync.h>
+
+#include <memory>
+#include <vector>
+
+namespace android::renderengine::skia {
+
+std::unique_ptr<GraphiteVkRenderEngine> GraphiteVkRenderEngine::create(
+        const RenderEngineCreationArgs& args) {
+    std::unique_ptr<GraphiteVkRenderEngine> engine(new GraphiteVkRenderEngine(args));
+    engine->ensureContextsCreated();
+
+    if (getVulkanInterface(false).isInitialized()) {
+        ALOGD("GraphiteVkRenderEngine::%s: successfully initialized GraphiteVkRenderEngine",
+              __func__);
+        return engine;
+    } else {
+        ALOGE("GraphiteVkRenderEngine::%s: could not create GraphiteVkRenderEngine. "
+              "Likely insufficient Vulkan support",
+              __func__);
+        return {};
+    }
+}
+
+// Graphite-specific function signature for fFinishedProc callback.
+static void unref_semaphore(void* semaphore, skgpu::CallbackResult result) {
+    if (result != skgpu::CallbackResult::kSuccess) {
+        ALOGE("Graphite submission of work to GPU failed, check for Skia errors");
+    }
+    SkiaVkRenderEngine::DestroySemaphoreInfo* info =
+            reinterpret_cast<SkiaVkRenderEngine::DestroySemaphoreInfo*>(semaphore);
+    info->unref();
+}
+
+std::unique_ptr<SkiaGpuContext> GraphiteVkRenderEngine::createContext(
+        VulkanInterface& vulkanInterface) {
+    return SkiaGpuContext::MakeVulkan_Graphite(vulkanInterface.getGraphiteBackendContext());
+}
+
+void GraphiteVkRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) {
+    if (fenceFd.get() < 0) return;
+
+    int dupedFd = dup(fenceFd.get());
+    if (dupedFd < 0) {
+        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
+        sync_wait(fenceFd.get(), -1);
+        return;
+    }
+
+    base::unique_fd fenceDup(dupedFd);
+    VkSemaphore waitSemaphore =
+            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
+    auto beSemaphore = graphite::BackendSemaphores::MakeVulkan(waitSemaphore);
+    mStagedWaitSemaphores.push_back(beSemaphore);
+}
+
+base::unique_fd GraphiteVkRenderEngine::flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface>) {
+    // Minimal Recording setup. Required even if there are no incoming semaphores to wait on, and if
+    // creating the outgoing signaling semaphore fails.
+    std::unique_ptr<graphite::Recording> recording = context->graphiteRecorder()->snap();
+    graphite::InsertRecordingInfo insertInfo;
+    insertInfo.fRecording = recording.get();
+
+    VulkanInterface& vulkanInterface = getVulkanInterface(isProtected());
+    // This "signal" semaphore is called after rendering, but it is cleaned up in the same mechanism
+    // as "wait" semaphores from waitFence.
+    VkSemaphore vkSignalSemaphore = vulkanInterface.createExportableSemaphore();
+    auto backendSignalSemaphore = graphite::BackendSemaphores::MakeVulkan(vkSignalSemaphore);
+
+    // Collect all Vk semaphores that DestroySemaphoreInfo needs to own and delete after GPU work.
+    std::vector<VkSemaphore> vkSemaphoresToCleanUp;
+    if (vkSignalSemaphore != VK_NULL_HANDLE) {
+        vkSemaphoresToCleanUp.push_back(vkSignalSemaphore);
+    }
+    for (auto backendWaitSemaphore : mStagedWaitSemaphores) {
+        vkSemaphoresToCleanUp.push_back(
+            graphite::BackendSemaphores::GetVkSemaphore(backendWaitSemaphore));
+    }
+
+    DestroySemaphoreInfo* destroySemaphoreInfo = nullptr;
+    if (vkSemaphoresToCleanUp.size() > 0) {
+        destroySemaphoreInfo =
+                new DestroySemaphoreInfo(vulkanInterface, std::move(vkSemaphoresToCleanUp));
+
+        insertInfo.fNumWaitSemaphores = mStagedWaitSemaphores.size();
+        insertInfo.fWaitSemaphores = mStagedWaitSemaphores.data();
+        insertInfo.fNumSignalSemaphores = 1;
+        insertInfo.fSignalSemaphores = &backendSignalSemaphore;
+        insertInfo.fFinishedProc = unref_semaphore;
+        insertInfo.fFinishedContext = destroySemaphoreInfo;
+    }
+
+    const bool inserted = context->graphiteContext()->insertRecording(insertInfo);
+    LOG_ALWAYS_FATAL_IF(!inserted,
+                        "graphite::Context::insertRecording(...) failed, check for Skia errors");
+    const bool submitted = context->graphiteContext()->submit(graphite::SyncToCpu::kNo);
+    LOG_ALWAYS_FATAL_IF(!submitted, "graphite::Context::submit(...) failed, check for Skia errors");
+    // Skia's "backend" semaphores can be deleted immediately after inserting the recording; only
+    // the underlying VK semaphores need to be kept until GPU work is complete.
+    mStagedWaitSemaphores.clear();
+
+    base::unique_fd drawFenceFd(-1);
+    if (vkSignalSemaphore != VK_NULL_HANDLE) {
+        drawFenceFd.reset(vulkanInterface.exportSemaphoreSyncFd(vkSignalSemaphore));
+    }
+    // Now that drawFenceFd has been created, we can delete RE's reference to this semaphore, as
+    // another reference is still held until fFinishedProc is called after completion of GPU work.
+    if (destroySemaphoreInfo) {
+        destroySemaphoreInfo->unref();
+    }
+    return drawFenceFd;
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/GraphiteVkRenderEngine.h b/libs/renderengine/skia/GraphiteVkRenderEngine.h
new file mode 100644
index 0000000..cf24a3b
--- /dev/null
+++ b/libs/renderengine/skia/GraphiteVkRenderEngine.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2024 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 "SkiaVkRenderEngine.h"
+
+#include <include/gpu/graphite/BackendSemaphore.h>
+
+namespace android::renderengine::skia {
+
+class GraphiteVkRenderEngine : public SkiaVkRenderEngine {
+public:
+    static std::unique_ptr<GraphiteVkRenderEngine> create(const RenderEngineCreationArgs& args);
+
+protected:
+    std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
+
+private:
+    GraphiteVkRenderEngine(const RenderEngineCreationArgs& args) : SkiaVkRenderEngine(args) {}
+
+    std::vector<graphite::BackendSemaphore> mStagedWaitSemaphores;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index fea4129..4ef7d5b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -23,15 +23,15 @@
 
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
-#include <GrContextOptions.h>
-#include <GrTypes.h>
 #include <android-base/stringprintf.h>
-#include <gl/GrGLInterface.h>
+#include <common/trace.h>
+#include <include/gpu/ganesh/GrContextOptions.h>
+#include <include/gpu/ganesh/GrTypes.h>
 #include <include/gpu/ganesh/gl/GrGLDirectContext.h>
-#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
+#include <log/log_main.h>
 #include <sync/sync.h>
 #include <ui/DebugUtils.h>
-#include <utils/Trace.h>
 
 #include <cmath>
 #include <cstdint>
@@ -39,7 +39,7 @@
 #include <numeric>
 
 #include "GLExtensions.h"
-#include "log/log_main.h"
+#include "compat/SkiaGpuContext.h"
 
 namespace android {
 namespace renderengine {
@@ -207,7 +207,7 @@
     std::unique_ptr<SkiaGLRenderEngine> engine(new SkiaGLRenderEngine(args, display, ctxt,
                                                                       placeholder, protectedContext,
                                                                       protectedPlaceholder));
-    engine->ensureGrContextsCreated();
+    engine->ensureContextsCreated();
 
     ALOGI("OpenGL ES informations:");
     ALOGI("vendor    : %s", extensions.getVendor());
@@ -269,7 +269,7 @@
                                        EGLContext ctxt, EGLSurface placeholder,
                                        EGLContext protectedContext, EGLSurface protectedPlaceholder)
       : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur),
+                         args.blurAlgorithm),
         mEGLDisplay(display),
         mEGLContext(ctxt),
         mPlaceholderSurface(placeholder),
@@ -277,7 +277,7 @@
         mProtectedPlaceholderSurface(protectedPlaceholder) {}
 
 SkiaGLRenderEngine::~SkiaGLRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
     if (mPlaceholderSurface != EGL_NO_SURFACE) {
         eglDestroySurface(mEGLDisplay, mPlaceholderSurface);
     }
@@ -295,9 +295,7 @@
     eglReleaseThread();
 }
 
-SkiaRenderEngine::Contexts SkiaGLRenderEngine::createDirectContexts(
-    const GrContextOptions& options) {
-
+SkiaRenderEngine::Contexts SkiaGLRenderEngine::createContexts() {
     LOG_ALWAYS_FATAL_IF(isProtected(),
                         "Cannot setup contexts while already in protected mode");
 
@@ -306,10 +304,10 @@
     LOG_ALWAYS_FATAL_IF(!glInterface.get(), "GrGLMakeNativeInterface() failed");
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContexts::MakeGL(glInterface, options);
+    contexts.first = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
     if (supportsProtectedContentImpl()) {
         useProtectedContextImpl(GrProtected::kYes);
-        contexts.second = GrDirectContexts::MakeGL(glInterface, options);
+        contexts.second = SkiaGpuContext::MakeGL_Ganesh(glInterface, mSkSLCacheMonitor);
         useProtectedContextImpl(GrProtected::kNo);
     }
 
@@ -330,25 +328,30 @@
     return eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE;
 }
 
-void SkiaGLRenderEngine::waitFence(GrDirectContext*, base::borrowed_fd fenceFd) {
+void SkiaGLRenderEngine::waitFence(SkiaGpuContext*, base::borrowed_fd fenceFd) {
     if (fenceFd.get() >= 0 && !waitGpuFence(fenceFd)) {
-        ATRACE_NAME("SkiaGLRenderEngine::waitFence");
+        SFTRACE_NAME("SkiaGLRenderEngine::waitFence");
         sync_wait(fenceFd.get(), -1);
     }
 }
 
-base::unique_fd SkiaGLRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
-    base::unique_fd drawFence = flush();
+base::unique_fd SkiaGLRenderEngine::flushAndSubmit(SkiaGpuContext* context,
+                                                   sk_sp<SkSurface> dstSurface) {
+    sk_sp<GrDirectContext> grContext = context->grDirectContext();
+    {
+        SFTRACE_NAME("flush surface");
+        grContext->flush(dstSurface.get());
+    }
+    base::unique_fd drawFence = flushGL();
 
     bool requireSync = drawFence.get() < 0;
     if (requireSync) {
-        ATRACE_BEGIN("Submit(sync=true)");
+        SFTRACE_BEGIN("Submit(sync=true)");
     } else {
-        ATRACE_BEGIN("Submit(sync=false)");
+        SFTRACE_BEGIN("Submit(sync=false)");
     }
-    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes :
-                                                   GrSyncCpu::kNo);
-    ATRACE_END();
+    bool success = grContext->submit(requireSync ? GrSyncCpu::kYes : GrSyncCpu::kNo);
+    SFTRACE_END();
     if (!success) {
         ALOGE("Failed to flush RenderEngine commands");
         // Chances are, something illegal happened (Skia's internal GPU object
@@ -394,8 +397,8 @@
     return true;
 }
 
-base::unique_fd SkiaGLRenderEngine::flush() {
-    ATRACE_CALL();
+base::unique_fd SkiaGLRenderEngine::flushGL() {
+    SFTRACE_CALL();
     if (!GLExtensions::getInstance().hasNativeFenceSync()) {
         return base::unique_fd();
     }
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index af33110..7651038 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -20,9 +20,10 @@
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
 #include <GLES2/gl2.h>
-#include <GrDirectContext.h>
 #include <SkSurface.h>
 #include <android-base/thread_annotations.h>
+#include <include/gpu/ganesh/GrContextOptions.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
 #include <sys/types.h>
@@ -32,7 +33,6 @@
 
 #include "AutoBackendTexture.h"
 #include "EGL/egl.h"
-#include "GrContextOptions.h"
 #include "SkImageInfo.h"
 #include "SkiaRenderEngine.h"
 #include "android-base/macros.h"
@@ -59,11 +59,11 @@
 protected:
     // Implementations of abstract SkiaRenderEngine functions specific to
     // rendering backend
-    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    virtual SkiaRenderEngine::Contexts createContexts();
     bool supportsProtectedContentImpl() const override;
     bool useProtectedContextImpl(GrProtected isProtected) override;
-    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
-    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
+    void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override;
+    base::unique_fd flushAndSubmit(SkiaGpuContext* context, sk_sp<SkSurface> dstSurface) override;
     void appendBackendSpecificInfoToDump(std::string& result) override;
 
 private:
@@ -71,7 +71,7 @@
                        EGLSurface placeholder, EGLContext protectedContext,
                        EGLSurface protectedPlaceholder);
     bool waitGpuFence(base::borrowed_fd fenceFd);
-    base::unique_fd flush();
+    base::unique_fd flushGL();
     static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
     static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
                                        EGLContext shareContext,
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 6e393f0..e62639f 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -20,9 +20,6 @@
 
 #include "SkiaRenderEngine.h"
 
-#include <GrBackendSemaphore.h>
-#include <GrContextOptions.h>
-#include <GrTypes.h>
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
@@ -54,8 +51,11 @@
 #include <SkTileMode.h>
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <gui/FenceMonitor.h>
-#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/GrBackendSemaphore.h>
+#include <include/gpu/ganesh/GrContextOptions.h>
+#include <include/gpu/ganesh/GrTypes.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <pthread.h>
 #include <src/core/SkTraceEventCommon.h>
@@ -64,7 +64,6 @@
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/HdrRenderTypeUtils.h>
-#include <utils/Trace.h>
 
 #include <cmath>
 #include <cstdint>
@@ -74,11 +73,16 @@
 
 #include "Cache.h"
 #include "ColorSpaces.h"
+#include "compat/SkiaGpuContext.h"
 #include "filters/BlurFilter.h"
+#include "filters/GainmapFactory.h"
 #include "filters/GaussianBlurFilter.h"
+#include "filters/KawaseBlurDualFilter.h"
 #include "filters/KawaseBlurFilter.h"
 #include "filters/LinearEffect.h"
+#include "filters/MouriMap.h"
 #include "log/log_main.h"
+#include "skia/compat/SkiaBackendTexture.h"
 #include "skia/debug/SkiaCapture.h"
 #include "skia/debug/SkiaMemoryReporter.h"
 #include "skia/filters/StretchShaderFactory.h"
@@ -88,8 +92,7 @@
 
 // Debugging settings
 static const bool kPrintLayerSettings = false;
-static const bool kFlushAfterEveryLayer = kPrintLayerSettings;
-static constexpr bool kEnableLayerBrightening = true;
+static const bool kGaneshFlushAfterEveryLayer = kPrintLayerSettings;
 
 } // namespace
 
@@ -236,16 +239,26 @@
 static inline SkPoint3 getSkPoint3(const android::vec3& vector) {
     return SkPoint3::Make(vector.x, vector.y, vector.z);
 }
+
 } // namespace
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
+namespace {
+void trace(sp<Fence> fence) {
+    if (SFTRACE_ENABLED()) {
+        static gui::FenceMonitor sMonitor("RE Completion");
+        sMonitor.queueFence(std::move(fence));
+    }
+}
+} // namespace
+
 using base::StringAppendF;
 
-std::future<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
-    Cache::primeShaderCache(this, shouldPrimeUltraHDR);
+std::future<void> SkiaRenderEngine::primeCache(PrimeCacheConfig config) {
+    Cache::primeShaderCache(this, config);
     return {};
 }
 
@@ -259,7 +272,7 @@
                                                const SkString& description) {
     mShadersCachedSinceLastCall++;
     mTotalShadersCompiled++;
-    ATRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled);
+    SFTRACE_FORMAT("SF cache: %i shaders", mTotalShadersCompiled);
 }
 
 int SkiaRenderEngine::reportShadersCompiled() {
@@ -271,34 +284,52 @@
 }
 
 SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat,
-                                   bool supportsBackgroundBlur)
+                                   BlurAlgorithm blurAlgorithm)
       : RenderEngine(threaded), mDefaultPixelFormat(pixelFormat) {
-    if (supportsBackgroundBlur) {
-        ALOGD("Background Blurs Enabled");
-        mBlurFilter = new KawaseBlurFilter();
+    switch (blurAlgorithm) {
+        case BlurAlgorithm::GAUSSIAN: {
+            ALOGD("Background Blurs Enabled (Gaussian algorithm)");
+            mBlurFilter = new GaussianBlurFilter();
+            break;
+        }
+        case BlurAlgorithm::KAWASE: {
+            ALOGD("Background Blurs Enabled (Kawase algorithm)");
+            mBlurFilter = new KawaseBlurFilter();
+            break;
+        }
+        case BlurAlgorithm::KAWASE_DUAL_FILTER: {
+            ALOGD("Background Blurs Enabled (Kawase dual-filtering algorithm)");
+            mBlurFilter = new KawaseBlurDualFilter();
+            break;
+        }
+        default: {
+            mBlurFilter = nullptr;
+            break;
+        }
     }
+
     mCapture = std::make_unique<SkiaCapture>();
 }
 
 SkiaRenderEngine::~SkiaRenderEngine() { }
 
-// To be called from backend dtors.
-void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+// To be called from backend dtors. Used to clean up Skia objects before GPU API contexts are
+// destroyed by subclasses.
+void SkiaRenderEngine::finishRenderingAndAbandonContexts() {
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
     if (mBlurFilter) {
         delete mBlurFilter;
     }
 
-    if (mGrContext) {
-        mGrContext->flushAndSubmit(GrSyncCpu::kYes);
-        mGrContext->abandonContext();
-    }
+    // Leftover textures may hold refs to backend-specific Skia contexts, which must be released
+    // before ~SkiaGpuContext is called.
+    mTextureCleanupMgr.setDeferredStatus(false);
+    mTextureCleanupMgr.cleanup();
 
-    if (mProtectedGrContext) {
-        mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
-        mProtectedGrContext->abandonContext();
-    }
+    // ~SkiaGpuContext must be called before GPU API contexts are torn down.
+    mContext.reset();
+    mProtectedContext.reset();
 }
 
 void SkiaRenderEngine::useProtectedContext(bool useProtectedContext) {
@@ -308,24 +339,24 @@
     }
 
     // release any scratch resources before switching into a new mode
-    if (getActiveGrContext()) {
-        getActiveGrContext()->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+    if (getActiveContext()) {
+        getActiveContext()->purgeUnlockedScratchResources();
     }
 
     // Backend-specific way to switch to protected context
     if (useProtectedContextImpl(
             useProtectedContext ? GrProtected::kYes : GrProtected::kNo)) {
         mInProtectedContext = useProtectedContext;
-        // given that we are sharing the same thread between two GrContexts we need to
+        // given that we are sharing the same thread between two contexts we need to
         // make sure that the thread state is reset when switching between the two.
-        if (getActiveGrContext()) {
-            getActiveGrContext()->resetContext();
+        if (getActiveContext()) {
+            getActiveContext()->resetContextIfApplicable();
         }
     }
 }
 
-GrDirectContext* SkiaRenderEngine::getActiveGrContext() {
-    return mInProtectedContext ? mProtectedGrContext.get() : mGrContext.get();
+SkiaGpuContext* SkiaRenderEngine::getActiveContext() {
+    return mInProtectedContext ? mProtectedContext.get() : mContext.get();
 }
 
 static float toDegrees(uint32_t transform) {
@@ -374,17 +405,12 @@
             sourceTransfer != destTransfer;
 }
 
-void SkiaRenderEngine::ensureGrContextsCreated() {
-    if (mGrContext) {
+void SkiaRenderEngine::ensureContextsCreated() {
+    if (mContext) {
         return;
     }
 
-    GrContextOptions options;
-    options.fDisableDriverCorrectnessWorkarounds = true;
-    options.fDisableDistanceFieldPaths = true;
-    options.fReducedShaderVariations = true;
-    options.fPersistentCache = &mSkSLCacheMonitor;
-    std::tie(mGrContext, mProtectedGrContext) = createDirectContexts(options);
+    std::tie(mContext, mProtectedContext) = createContexts();
 }
 
 void SkiaRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
@@ -406,14 +432,12 @@
     if (isProtectedBuffer || isProtected() || !isGpuSampleable) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // If we were to support caching protected buffers then we will need to switch the
     // currently bound context if we are not already using the protected context (and subsequently
-    // switch back after the buffer is cached).  However, for non-protected content we can bind
-    // the texture in either GL context because they are initialized with the same share_context
-    // which allows the texture state to be shared between them.
-    auto grContext = getActiveGrContext();
+    // switch back after the buffer is cached).
+    auto context = getActiveContext();
     auto& cache = mTextureCache;
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
@@ -423,16 +447,17 @@
         if (FlagManager::getInstance().renderable_buffer_usage()) {
             isRenderable = buffer->getUsage() & GRALLOC_USAGE_HW_RENDER;
         }
-        std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
-                std::make_shared<AutoBackendTexture::LocalRef>(grContext,
-                                                               buffer->toAHardwareBuffer(),
-                                                               isRenderable, mTextureCleanupMgr);
+        std::unique_ptr<SkiaBackendTexture> backendTexture =
+                context->makeBackendTexture(buffer->toAHardwareBuffer(), isRenderable);
+        auto imageTextureRef =
+                std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+                                                               mTextureCleanupMgr);
         cache.insert({buffer->getId(), imageTextureRef});
     }
 }
 
 void SkiaRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
         iter != mGraphicBufferExternalRefs.end()) {
@@ -477,9 +502,10 @@
             return it->second;
         }
     }
-    return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
-                                                          buffer->toAHardwareBuffer(),
-                                                          isOutputBuffer, mTextureCleanupMgr);
+    std::unique_ptr<SkiaBackendTexture> backendTexture =
+            getActiveContext()->makeBackendTexture(buffer->toAHardwareBuffer(), isOutputBuffer);
+    return std::make_shared<AutoBackendTexture::LocalRef>(std::move(backendTexture),
+                                                          mTextureCleanupMgr);
 }
 
 bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
@@ -488,7 +514,7 @@
 }
 
 void SkiaRenderEngine::cleanupPostRender() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mRenderingMutex);
     mTextureCleanupMgr.cleanup();
 }
@@ -497,21 +523,54 @@
         const RuntimeEffectShaderParameters& parameters) {
     // The given surface will be stretched by HWUI via matrix transformation
     // which gets similar results for most surfaces
-    // Determine later on if we need to leverage the stertch shader within
+    // Determine later on if we need to leverage the stretch shader within
     // surface flinger
     const auto& stretchEffect = parameters.layer.stretchEffect;
+    const auto& targetBuffer = parameters.layer.source.buffer.buffer;
+    const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
+
     auto shader = parameters.shader;
-    if (stretchEffect.hasEffect()) {
-        const auto targetBuffer = parameters.layer.source.buffer.buffer;
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
-        if (graphicBuffer && parameters.shader) {
+    if (graphicBuffer && parameters.shader) {
+        if (stretchEffect.hasEffect()) {
             shader = mStretchShaderFactory.createSkShader(shader, stretchEffect);
         }
+        // The given surface requires to be filled outside of its buffer bounds if the edge
+        // extension is required
+        const auto& edgeExtensionEffect = parameters.layer.edgeExtensionEffect;
+        if (edgeExtensionEffect.hasEffect()) {
+            shader = mEdgeExtensionShaderFactory.createSkShader(shader, parameters.layer,
+                                                                parameters.imageBounds);
+        }
     }
 
     if (parameters.requiresLinearEffect) {
+        const auto format = targetBuffer != nullptr
+                ? std::optional<ui::PixelFormat>(
+                          static_cast<ui::PixelFormat>(targetBuffer->getPixelFormat()))
+                : std::nullopt;
+
+        const auto hdrType = getHdrRenderType(parameters.layer.sourceDataspace, format,
+                                              parameters.layerDimmingRatio);
+
+        const auto usingLocalTonemap =
+                parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local &&
+                hdrType != HdrRenderType::SDR &&
+                shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr) &&
+                (hdrType != HdrRenderType::DISPLAY_HDR ||
+                 parameters.display.targetHdrSdrRatio < parameters.layerDimmingRatio);
+        if (usingLocalTonemap) {
+            const float inputRatio =
+                    hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
+            static MouriMap kMapper;
+            shader = kMapper.mouriMap(getActiveContext(), shader, inputRatio,
+                                      parameters.display.targetHdrSdrRatio);
+        }
+
+        // disable tonemapping if we already locally tonemapped
+        auto inputDataspace =
+                usingLocalTonemap ? parameters.outputDataSpace : parameters.layer.sourceDataspace;
         auto effect =
-                shaders::LinearEffect{.inputDataspace = parameters.layer.sourceDataspace,
+                shaders::LinearEffect{.inputDataspace = inputDataspace,
                                       .outputDataspace = parameters.outputDataSpace,
                                       .undoPremultipliedAlpha = parameters.undoPremultipliedAlpha,
                                       .fakeOutputDataspace = parameters.fakeOutputDataspace};
@@ -527,20 +586,20 @@
 
         mat4 colorTransform = parameters.layer.colorTransform;
 
-        colorTransform *=
-                mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
-                                 parameters.layerDimmingRatio, 1.f));
+        if (!usingLocalTonemap) {
+            colorTransform *=
+                    mat4::scale(vec4(parameters.layerDimmingRatio, parameters.layerDimmingRatio,
+                                     parameters.layerDimmingRatio, 1.f));
+        }
 
-        const auto targetBuffer = parameters.layer.source.buffer.buffer;
-        const auto graphicBuffer = targetBuffer ? targetBuffer->getBuffer() : nullptr;
         const auto hardwareBuffer = graphicBuffer ? graphicBuffer->toAHardwareBuffer() : nullptr;
-        return createLinearEffectShader(parameters.shader, effect, runtimeEffect,
-                                        std::move(colorTransform), parameters.display.maxLuminance,
+        return createLinearEffectShader(shader, effect, runtimeEffect, std::move(colorTransform),
+                                        parameters.display.maxLuminance,
                                         parameters.display.currentLuminanceNits,
                                         parameters.layer.source.buffer.maxLuminanceNits,
                                         hardwareBuffer, parameters.display.renderIntent);
     }
-    return parameters.shader;
+    return shader;
 }
 
 void SkiaRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
@@ -655,7 +714,7 @@
         const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
         const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
-    ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
+    SFTRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
 
     std::lock_guard<std::mutex> lock(mRenderingMutex);
 
@@ -667,9 +726,9 @@
 
     validateOutputBufferUsage(buffer->getBuffer());
 
-    auto grContext = getActiveGrContext();
-    LOG_ALWAYS_FATAL_IF(grContext->abandoned(), "GrContext is abandoned/device lost at start of %s",
-                        __func__);
+    auto context = getActiveContext();
+    LOG_ALWAYS_FATAL_IF(context->isAbandonedOrDeviceLost(),
+                        "Context is abandoned/device lost at start of %s", __func__);
 
     // any AutoBackendTexture deletions will now be deferred until cleanupPostRender is called
     DeferTextureCleanup dtc(mTextureCleanupMgr);
@@ -677,10 +736,9 @@
     auto surfaceTextureRef = getOrCreateBackendTexture(buffer->getBuffer(), true);
 
     // wait on the buffer to be ready to use prior to using it
-    waitFence(grContext, bufferFence);
+    waitFence(context, bufferFence);
 
-    sk_sp<SkSurface> dstSurface =
-            surfaceTextureRef->getOrCreateSurface(display.outputDataspace, grContext);
+    sk_sp<SkSurface> dstSurface = surfaceTextureRef->getOrCreateSurface(display.outputDataspace);
 
     SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
     if (dstCanvas == nullptr) {
@@ -704,9 +762,7 @@
             [&](const auto& l) { return l.whitePointNits; });
 
     // ...and compute the dimming ratio if dimming is requested
-    const float displayDimmingRatio = display.targetLuminanceNits > 0.f &&
-                    maxLayerWhitePoint > 0.f &&
-                    (kEnableLayerBrightening || display.targetLuminanceNits > maxLayerWhitePoint)
+    const float displayDimmingRatio = display.targetLuminanceNits > 0.f && maxLayerWhitePoint > 0.f
             ? maxLayerWhitePoint / display.targetLuminanceNits
             : 1.f;
 
@@ -750,7 +806,7 @@
         logSettings(display);
     }
     for (const auto& layer : layers) {
-        ATRACE_FORMAT("DrawLayer: %s", layer.name.c_str());
+        SFTRACE_FORMAT("DrawLayer: %s", layer.name.c_str());
 
         if (kPrintLayerSettings) {
             logSettings(layer);
@@ -844,8 +900,8 @@
             // TODO(b/182216890): Filter out empty layers earlier
             if (blurRect.width() > 0 && blurRect.height() > 0) {
                 if (layer.backgroundBlurRadius > 0) {
-                    ATRACE_NAME("BackgroundBlur");
-                    auto blurredImage = mBlurFilter->generate(grContext, layer.backgroundBlurRadius,
+                    SFTRACE_NAME("BackgroundBlur");
+                    auto blurredImage = mBlurFilter->generate(context, layer.backgroundBlurRadius,
                                                               blurInput, blurRect);
 
                     cachedBlurs[layer.backgroundBlurRadius] = blurredImage;
@@ -857,9 +913,9 @@
                 canvas->concat(getSkM44(layer.blurRegionTransform).asM33());
                 for (auto region : layer.blurRegions) {
                     if (cachedBlurs[region.blurRadius] == nullptr) {
-                        ATRACE_NAME("BlurRegion");
+                        SFTRACE_NAME("BlurRegion");
                         cachedBlurs[region.blurRadius] =
-                                mBlurFilter->generate(grContext, region.blurRadius, blurInput,
+                                mBlurFilter->generate(context, region.blurRadius, blurInput,
                                                       blurRect);
                     }
 
@@ -940,15 +996,15 @@
 
         SkPaint paint;
         if (layer.source.buffer.buffer) {
-            ATRACE_NAME("DrawImage");
+            SFTRACE_NAME("DrawImage");
             validateInputBufferUsage(layer.source.buffer.buffer->getBuffer());
             const auto& item = layer.source.buffer;
             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
+            // if the layer's buffer has a fence, then we must respect the fence prior to using
             // the buffer.
             if (layer.source.buffer.fence != nullptr) {
-                waitFence(grContext, layer.source.buffer.fence->get());
+                waitFence(context, layer.source.buffer.fence->get());
             }
 
             // isOpaque means we need to ignore the alpha in the image,
@@ -972,7 +1028,7 @@
                     : item.isOpaque                      ? kOpaque_SkAlphaType
                     : item.usePremultipliedAlpha         ? kPremul_SkAlphaType
                                                          : kUnpremul_SkAlphaType;
-            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType, grContext);
+            sk_sp<SkImage> image = imageTextureRef->makeImage(layerDataspace, alphaType);
 
             auto texMatrix = getSkM44(item.textureTransform).asM33();
             // textureTansform was intended to be passed directly into a shader, so when
@@ -1007,18 +1063,20 @@
                                                            toSkColorSpace(layerDataspace)));
             }
 
-            paint.setShader(createRuntimeEffectShader(
-                    RuntimeEffectShaderParameters{.shader = shader,
-                                                  .layer = layer,
-                                                  .display = display,
-                                                  .undoPremultipliedAlpha = !item.isOpaque &&
-                                                          item.usePremultipliedAlpha,
-                                                  .requiresLinearEffect = requiresLinearEffect,
-                                                  .layerDimmingRatio = dimInLinearSpace
-                                                          ? layerDimmingRatio
-                                                          : 1.f,
-                                                  .outputDataSpace = display.outputDataspace,
-                                                  .fakeOutputDataspace = fakeDataspace}));
+            SkRect imageBounds;
+            matrix.mapRect(&imageBounds, SkRect::Make(image->bounds()));
+
+            paint.setShader(createRuntimeEffectShader(RuntimeEffectShaderParameters{
+                    .shader = shader,
+                    .layer = layer,
+                    .display = display,
+                    .undoPremultipliedAlpha = !item.isOpaque && item.usePremultipliedAlpha,
+                    .requiresLinearEffect = requiresLinearEffect,
+                    .layerDimmingRatio = dimInLinearSpace ? layerDimmingRatio : 1.f,
+                    .outputDataSpace = display.outputDataspace,
+                    .fakeOutputDataspace = fakeDataspace,
+                    .imageBounds = imageBounds,
+            }));
 
             // Turn on dithering when dimming beyond this (arbitrary) threshold...
             static constexpr float kDimmingThreshold = 0.9f;
@@ -1071,7 +1129,7 @@
                 paint.setColorFilter(SkColorFilters::Matrix(colorMatrix));
             }
         } else {
-            ATRACE_NAME("DrawColor");
+            SFTRACE_NAME("DrawColor");
             const auto color = layer.source.solidColor;
             sk_sp<SkShader> shader = SkShaders::Color(SkColor4f{.fR = color.r,
                                                                 .fG = color.g,
@@ -1086,7 +1144,8 @@
                                                   .requiresLinearEffect = requiresLinearEffect,
                                                   .layerDimmingRatio = layerDimmingRatio,
                                                   .outputDataSpace = display.outputDataspace,
-                                                  .fakeOutputDataspace = fakeDataspace}));
+                                                  .fakeOutputDataspace = fakeDataspace,
+                                                  .imageBounds = SkRect::MakeEmpty()}));
         }
 
         if (layer.disableBlending) {
@@ -1126,60 +1185,78 @@
         } else {
             canvas->drawRect(bounds.rect(), paint);
         }
-        if (kFlushAfterEveryLayer) {
-            ATRACE_NAME("flush surface");
+        if (kGaneshFlushAfterEveryLayer) {
+            SFTRACE_NAME("flush surface");
+            // No-op in Graphite. If "flushing" Skia's drawing commands after each layer is desired
+            // in Graphite, then a graphite::Recording would need to be snapped and tracked for each
+            // layer, which is likely possible but adds non-trivial complexity (in both bookkeeping
+            // and refactoring).
             skgpu::ganesh::Flush(activeSurface);
         }
     }
-    for (const auto& borderRenderInfo : display.borderInfoList) {
-        SkPaint p;
-        p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
-                             borderRenderInfo.color.b, borderRenderInfo.color.a});
-        p.setAntiAlias(true);
-        p.setStyle(SkPaint::kStroke_Style);
-        p.setStrokeWidth(borderRenderInfo.width);
-        SkRegion sk_region;
-        SkPath path;
-
-        // Construct a final SkRegion using Regions
-        for (const auto& r : borderRenderInfo.combinedRegion) {
-            sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
-        }
-
-        sk_region.getBoundaryPath(&path);
-        canvas->drawPath(path, p);
-        path.close();
-    }
 
     surfaceAutoSaveRestore.restore();
     mCapture->endCapture();
-    {
-        ATRACE_NAME("flush surface");
-        LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
-        skgpu::ganesh::Flush(activeSurface);
-    }
 
-    auto drawFence = sp<Fence>::make(flushAndSubmit(grContext));
+    LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
+    auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
+    trace(drawFence);
+    resultPromise->set_value(std::move(drawFence));
+}
 
-    if (ATRACE_ENABLED()) {
-        static gui::FenceMonitor sMonitor("RE Completion");
-        sMonitor.queueFence(drawFence);
-    }
+void SkiaRenderEngine::drawGainmapInternal(
+        const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    auto context = getActiveContext();
+    auto surfaceTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
+    sk_sp<SkSurface> dstSurface =
+            surfaceTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
+
+    waitFence(context, sdrFence);
+    const auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), false);
+    const auto sdrImage = sdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
+    const auto sdrShader =
+            sdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                 nullptr);
+    waitFence(context, hdrFence);
+    const auto hdrTextureRef = getOrCreateBackendTexture(hdr->getBuffer(), false);
+    const auto hdrImage = hdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
+    const auto hdrShader =
+            hdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                 nullptr);
+
+    static GainmapFactory kGainmapFactory;
+    const auto gainmapShader = kGainmapFactory.createSkShader(sdrShader, hdrShader, hdrSdrRatio);
+
+    const auto canvas = dstSurface->getCanvas();
+    SkPaint paint;
+    paint.setShader(gainmapShader);
+    paint.setBlendMode(SkBlendMode::kSrc);
+    canvas->drawPaint(paint);
+
+    auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
+    trace(drawFence);
     resultPromise->set_value(std::move(drawFence));
 }
 
 size_t SkiaRenderEngine::getMaxTextureSize() const {
-    return mGrContext->maxTextureSize();
+    return mContext->getMaxTextureSize();
 }
 
 size_t SkiaRenderEngine::getMaxViewportDims() const {
-    return mGrContext->maxRenderTargetSize();
+    return mContext->getMaxRenderTargetSize();
 }
 
 void SkiaRenderEngine::drawShadow(SkCanvas* canvas,
                                   const SkRRect& casterRRect,
                                   const ShadowSettings& settings) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const float casterZ = settings.length / 2.0f;
     const auto flags =
             settings.casterIsTranslucent ? kTransparentOccluder_ShadowFlag : kNone_ShadowFlag;
@@ -1199,13 +1276,13 @@
     const int maxResourceBytes = size.width * size.height * SURFACE_SIZE_MULTIPLIER;
 
     // start by resizing the current context
-    getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+    getActiveContext()->setResourceCacheLimit(maxResourceBytes);
 
     // if it is possible to switch contexts then we will resize the other context
     const bool originalProtectedState = mInProtectedContext;
     useProtectedContext(!mInProtectedContext);
     if (mInProtectedContext != originalProtectedState) {
-        getActiveGrContext()->setResourceCacheLimit(maxResourceBytes);
+        getActiveContext()->setResourceCacheLimit(maxResourceBytes);
         // reset back to the initial context that was active when this method was called
         useProtectedContext(originalProtectedState);
     }
@@ -1245,7 +1322,7 @@
                 {"skia", "Other"},
         };
         SkiaMemoryReporter gpuReporter(gpuResourceMap, true);
-        mGrContext->dumpMemoryStatistics(&gpuReporter);
+        mContext->dumpMemoryStatistics(&gpuReporter);
         StringAppendF(&result, "Skia's GPU Caches: ");
         gpuReporter.logTotals(result);
         gpuReporter.logOutput(result);
@@ -1269,8 +1346,8 @@
         StringAppendF(&result, "\n");
 
         SkiaMemoryReporter gpuProtectedReporter(gpuResourceMap, true);
-        if (mProtectedGrContext) {
-            mProtectedGrContext->dumpMemoryStatistics(&gpuProtectedReporter);
+        if (mProtectedContext) {
+            mProtectedContext->dumpMemoryStatistics(&gpuProtectedReporter);
         }
         StringAppendF(&result, "Skia's GPU Protected Caches: ");
         gpuProtectedReporter.logTotals(result);
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index e88d44c..b5f8898 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -18,26 +18,26 @@
 #define SF_SKIARENDERENGINE_H_
 
 #include <renderengine/RenderEngine.h>
-#include <sys/types.h>
 
-#include <GrBackendSemaphore.h>
-#include <GrDirectContext.h>
-#include <SkSurface.h>
 #include <android-base/thread_annotations.h>
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/gpu/ganesh/GrBackendSemaphore.h>
+#include <include/gpu/ganesh/GrContextOptions.h>
 #include <renderengine/ExternalTexture.h>
 #include <renderengine/RenderEngine.h>
 #include <sys/types.h>
 
+#include <memory>
 #include <mutex>
 #include <unordered_map>
 
 #include "AutoBackendTexture.h"
-#include "GrContextOptions.h"
-#include "SkImageInfo.h"
-#include "SkiaRenderEngine.h"
 #include "android-base/macros.h"
+#include "compat/SkiaGpuContext.h"
 #include "debug/SkiaCapture.h"
 #include "filters/BlurFilter.h"
+#include "filters/EdgeExtensionShaderFactory.h"
 #include "filters/LinearEffect.h"
 #include "filters/StretchShaderFactory.h"
 
@@ -59,10 +59,10 @@
 class SkiaRenderEngine : public RenderEngine {
 public:
     static std::unique_ptr<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, bool supportsBackgroundBlur);
+    SkiaRenderEngine(Threaded, PixelFormat pixelFormat, BlurAlgorithm);
     ~SkiaRenderEngine() override;
 
-    std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
+    std::future<void> primeCache(PrimeCacheConfig config) override final;
     void cleanupPostRender() override final;
     bool supportsBackgroundBlur() override final {
         return mBlurFilter != nullptr;
@@ -76,24 +76,27 @@
     bool supportsProtectedContent() const override {
         return supportsProtectedContentImpl();
     }
-    void ensureGrContextsCreated();
+    void ensureContextsCreated();
+
 protected:
-    // This is so backends can stop the generic rendering state first before
-    // cleaning up backend-specific state
-    void finishRenderingAndAbandonContext();
+    // This is so backends can stop the generic rendering state first before cleaning up
+    // backend-specific state. SkiaGpuContexts are invalid after invocation.
+    void finishRenderingAndAbandonContexts();
 
     // Functions that a given backend (GLES, Vulkan) must implement
-    using Contexts = std::pair<sk_sp<GrDirectContext>, sk_sp<GrDirectContext>>;
-    virtual Contexts createDirectContexts(const GrContextOptions& options) = 0;
+    using Contexts = std::pair<unique_ptr<SkiaGpuContext>, unique_ptr<SkiaGpuContext>>;
+    virtual Contexts createContexts() = 0;
     virtual bool supportsProtectedContentImpl() const = 0;
     virtual bool useProtectedContextImpl(GrProtected isProtected) = 0;
-    virtual void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) = 0;
-    virtual base::unique_fd flushAndSubmit(GrDirectContext* context) = 0;
+    virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) = 0;
+    virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context,
+                                           sk_sp<SkSurface> dstSurface) = 0;
     virtual void appendBackendSpecificInfoToDump(std::string& result) = 0;
 
     size_t getMaxTextureSize() const override final;
     size_t getMaxViewportDims() const override final;
-    GrDirectContext* getActiveGrContext();
+    // TODO: b/293371537 - Return reference instead of pointer? (Cleanup)
+    SkiaGpuContext* getActiveContext();
 
     bool isProtected() const { return mInProtectedContext; }
 
@@ -121,6 +124,8 @@
         int mTotalShadersCompiled = 0;
     };
 
+    SkSLCacheMonitor mSkSLCacheMonitor;
+
 private:
     void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                   bool isRenderable) override final;
@@ -137,6 +142,13 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override final;
+    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                             const std::shared_ptr<ExternalTexture>& sdr,
+                             base::borrowed_fd&& sdrFence,
+                             const std::shared_ptr<ExternalTexture>& hdr,
+                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                             ui::Dataspace dataspace,
+                             const std::shared_ptr<ExternalTexture>& gainmap) override final;
 
     void dump(std::string& result) override final;
 
@@ -151,6 +163,7 @@
         float layerDimmingRatio;
         const ui::Dataspace outputDataSpace;
         const ui::Dataspace fakeOutputDataspace;
+        const SkRect& imageBounds;
     };
     sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
 
@@ -163,9 +176,6 @@
     // Number of external holders of ExternalTexture references, per GraphicBuffer ID.
     std::unordered_map<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
             GUARDED_BY(mRenderingMutex);
-    // 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>
@@ -173,6 +183,7 @@
     AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
 
     StretchShaderFactory mStretchShaderFactory;
+    EdgeExtensionShaderFactory mEdgeExtensionShaderFactory;
 
     sp<Fence> mLastDrawFence;
     BlurFilter* mBlurFilter = nullptr;
@@ -183,12 +194,11 @@
     // Mutex guarding rendering operations, so that internal state related to
     // rendering that is potentially modified by multiple threads is guaranteed thread-safe.
     mutable std::mutex mRenderingMutex;
-    SkSLCacheMonitor mSkSLCacheMonitor;
 
     // Graphics context used for creating surfaces and submitting commands
-    sk_sp<GrDirectContext> mGrContext;
+    unique_ptr<SkiaGpuContext> mContext;
     // Same as above, but for protected content (eg. DRM)
-    sk_sp<GrDirectContext> mProtectedGrContext;
+    unique_ptr<SkiaGpuContext> mProtectedContext;
     bool mInProtectedContext = false;
 };
 
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.cpp b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
index eb7a9d5..677a2b6 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.cpp
@@ -21,22 +21,22 @@
 
 #include "SkiaVkRenderEngine.h"
 
-#include <GrBackendSemaphore.h>
-#include <GrContextOptions.h>
-#include <vk/GrVkExtensions.h>
-#include <vk/GrVkTypes.h>
+#include "GaneshVkRenderEngine.h"
+#include "compat/SkiaGpuContext.h"
+
+#include <include/gpu/ganesh/GrBackendSemaphore.h>
+#include <include/gpu/ganesh/GrContextOptions.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSemaphore.h>
 #include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/ganesh/vk/GrVkTypes.h>
 
 #include <android-base/stringprintf.h>
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <sync/sync.h>
-#include <utils/Trace.h>
 
-#include <cstdint>
 #include <memory>
-#include <sstream>
 #include <string>
-#include <vector>
 
 #include <vulkan/vulkan.h>
 #include "log/log_main.h"
@@ -44,619 +44,19 @@
 namespace android {
 namespace renderengine {
 
-struct VulkanFuncs {
-    PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
-    PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
-    PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
-    PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
-
-    PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
-    PFN_vkDestroyDevice vkDestroyDevice = nullptr;
-    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) {}
-};
-
-namespace {
-void onVkDeviceFault(void* callbackContext, const std::string& description,
-                     const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
-                     const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
-                     const std::vector<std::byte>& vendorBinaryData);
-} // anonymous namespace
-
-struct VulkanInterface {
-    bool initialized = false;
-    VkInstance instance;
-    VkPhysicalDevice physicalDevice;
-    VkDevice device;
-    VkQueue queue;
-    int queueIndex;
-    uint32_t apiVersion;
-    GrVkExtensions grExtensions;
-    VkPhysicalDeviceFeatures2* physicalDeviceFeatures2 = nullptr;
-    VkPhysicalDeviceSamplerYcbcrConversionFeatures* samplerYcbcrConversionFeatures = nullptr;
-    VkPhysicalDeviceProtectedMemoryFeatures* protectedMemoryFeatures = nullptr;
-    VkPhysicalDeviceFaultFeaturesEXT* deviceFaultFeatures = nullptr;
-    GrVkGetProc grGetProc;
-    bool isProtected;
-    bool isRealtimePriority;
-
-    VulkanFuncs funcs;
-
-    std::vector<std::string> instanceExtensionNames;
-    std::vector<std::string> deviceExtensionNames;
-
-    GrVkBackendContext getBackendContext() {
-        GrVkBackendContext backendContext;
-        backendContext.fInstance = instance;
-        backendContext.fPhysicalDevice = physicalDevice;
-        backendContext.fDevice = device;
-        backendContext.fQueue = queue;
-        backendContext.fGraphicsQueueIndex = queueIndex;
-        backendContext.fMaxAPIVersion = apiVersion;
-        backendContext.fVkExtensions = &grExtensions;
-        backendContext.fDeviceFeatures2 = physicalDeviceFeatures2;
-        backendContext.fGetProc = grGetProc;
-        backendContext.fProtectedContext = isProtected ? GrProtected::kYes : GrProtected::kNo;
-        backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
-        backendContext.fDeviceLostProc = onVkDeviceFault;
-        return backendContext;
-    };
-
-    VkSemaphore createExportableSemaphore() {
-        VkExportSemaphoreCreateInfo exportInfo;
-        exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
-        exportInfo.pNext = nullptr;
-        exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkSemaphoreCreateInfo semaphoreInfo;
-        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-        semaphoreInfo.pNext = &exportInfo;
-        semaphoreInfo.flags = 0;
-
-        VkSemaphore semaphore;
-        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
-            return VK_NULL_HANDLE;
-        }
-
-        return semaphore;
-    }
-
-    // syncFd cannot be <= 0
-    VkSemaphore importSemaphoreFromSyncFd(int syncFd) {
-        VkSemaphoreCreateInfo semaphoreInfo;
-        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-        semaphoreInfo.pNext = nullptr;
-        semaphoreInfo.flags = 0;
-
-        VkSemaphore semaphore;
-        VkResult err = funcs.vkCreateSemaphore(device, &semaphoreInfo, nullptr, &semaphore);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to create import semaphore", __func__);
-            return VK_NULL_HANDLE;
-        }
-
-        VkImportSemaphoreFdInfoKHR importInfo;
-        importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
-        importInfo.pNext = nullptr;
-        importInfo.semaphore = semaphore;
-        importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
-        importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-        importInfo.fd = syncFd;
-
-        err = funcs.vkImportSemaphoreFdKHR(device, &importInfo);
-        if (VK_SUCCESS != err) {
-            funcs.vkDestroySemaphore(device, semaphore, nullptr);
-            ALOGE("%s: failed to import semaphore", __func__);
-            return VK_NULL_HANDLE;
-        }
-
-        return semaphore;
-    }
-
-    int exportSemaphoreSyncFd(VkSemaphore semaphore) {
-        int res;
-
-        VkSemaphoreGetFdInfoKHR getFdInfo;
-        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
-        getFdInfo.pNext = nullptr;
-        getFdInfo.semaphore = semaphore;
-        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkResult err = funcs.vkGetSemaphoreFdKHR(device, &getFdInfo, &res);
-        if (VK_SUCCESS != err) {
-            ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
-            return -1;
-        }
-        return res;
-    }
-
-    void destroySemaphore(VkSemaphore semaphore) {
-        funcs.vkDestroySemaphore(device, semaphore, nullptr);
-    }
-};
-
-namespace {
-void onVkDeviceFault(void* callbackContext, const std::string& description,
-                     const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
-                     const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
-                     const std::vector<std::byte>& vendorBinaryData) {
-    VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
-    const std::string protectedStr = interface->isProtected ? "protected" : "non-protected";
-    // The final crash string should contain as much differentiating info as possible, up to 1024
-    // bytes. As this final message is constructed, the same information is also dumped to the logs
-    // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
-    // statement is always placed first to give context.
-    ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
-    std::stringstream crashMsg;
-    crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
-
-    if (!addressInfos.empty()) {
-        ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
-        crashMsg << ", " << addressInfos.size() << " address info (";
-        for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
-            ALOGE(" addressType:       %d", (int)addressInfo.addressType);
-            ALOGE("  reportedAddress:  %" PRIu64, addressInfo.reportedAddress);
-            ALOGE("  addressPrecision: %" PRIu64, addressInfo.addressPrecision);
-            crashMsg << addressInfo.addressType << ":"
-                     << addressInfo.reportedAddress << ":"
-                     << addressInfo.addressPrecision << ", ";
-        }
-        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
-        crashMsg << ")";
-    }
-
-    if (!vendorInfos.empty()) {
-        ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
-        crashMsg << ", " << vendorInfos.size() << " vendor info (";
-        for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
-            ALOGE(" description:      %s", vendorInfo.description);
-            ALOGE("  vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
-            ALOGE("  vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
-            // Omit descriptions for individual vendor info structs in the crash string, as the
-            // fault code and fault data fields should be enough for clustering, and the verbosity
-            // isn't worth it. Additionally, vendors may just set the general description field of
-            // the overall fault to the description of the first element in this list, and that
-            // overall description will be placed at the end of the crash string.
-            crashMsg << vendorInfo.vendorFaultCode << ":"
-                     << vendorInfo.vendorFaultData << ", ";
-        }
-        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
-        crashMsg << ")";
-    }
-
-    if (!vendorBinaryData.empty()) {
-        // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
-        ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
-              " Stack team if you observe this message).",
-              vendorBinaryData.size());
-        crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
-    }
-
-    crashMsg << "): " << description;
-    LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
-};
-} // anonymous namespace
-
-static GrVkGetProc sGetProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
-    if (device != VK_NULL_HANDLE) {
-        return vkGetDeviceProcAddr(device, proc_name);
-    }
-    return vkGetInstanceProcAddr(instance, proc_name);
-};
-
-#define BAIL(fmt, ...)                                          \
-    {                                                           \
-        ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
-        return interface;                                       \
-    }
-
-#define CHECK_NONNULL(expr)       \
-    if ((expr) == nullptr) {      \
-        BAIL("[%s] null", #expr); \
-    }
-
-#define VK_CHECK(expr)                              \
-    if ((expr) != VK_SUCCESS) {                     \
-        BAIL("[%s] failed. err = %d", #expr, expr); \
-        return interface;                           \
-    }
-
-#define VK_GET_PROC(F)                                                           \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-#define VK_GET_INST_PROC(instance, F)                                      \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-#define VK_GET_DEV_PROC(device, F)                                     \
-    PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
-    CHECK_NONNULL(vk##F)
-
-VulkanInterface initVulkanInterface(bool protectedContent = false) {
-    const nsecs_t timeBefore = systemTime();
-    VulkanInterface interface;
-
-    VK_GET_PROC(EnumerateInstanceVersion);
-    uint32_t instanceVersion;
-    VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
-
-    if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        return interface;
-    }
-
-    const VkApplicationInfo appInfo = {
-            VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
-            VK_MAKE_VERSION(1, 1, 0),
-    };
-
-    VK_GET_PROC(EnumerateInstanceExtensionProperties);
-
-    uint32_t extensionCount = 0;
-    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
-    std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
-    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
-                                                    instanceExtensions.data()));
-    std::vector<const char*> enabledInstanceExtensionNames;
-    enabledInstanceExtensionNames.reserve(instanceExtensions.size());
-    interface.instanceExtensionNames.reserve(instanceExtensions.size());
-    for (const auto& instExt : instanceExtensions) {
-        enabledInstanceExtensionNames.push_back(instExt.extensionName);
-        interface.instanceExtensionNames.push_back(instExt.extensionName);
-    }
-
-    const VkInstanceCreateInfo instanceCreateInfo = {
-            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
-            nullptr,
-            0,
-            &appInfo,
-            0,
-            nullptr,
-            (uint32_t)enabledInstanceExtensionNames.size(),
-            enabledInstanceExtensionNames.data(),
-    };
-
-    VK_GET_PROC(CreateInstance);
-    VkInstance instance;
-    VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
-
-    VK_GET_INST_PROC(instance, DestroyInstance);
-    interface.funcs.vkDestroyInstance = vkDestroyInstance;
-    VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
-    VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
-    VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
-    VK_GET_INST_PROC(instance, CreateDevice);
-
-    uint32_t physdevCount;
-    VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
-    if (physdevCount == 0) {
-        BAIL("Could not find any physical devices");
-    }
-
-    physdevCount = 1;
-    VkPhysicalDevice physicalDevice;
-    VkResult enumeratePhysDevsErr =
-            vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
-    if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
-        BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
-             enumeratePhysDevsErr);
-    }
-
-    VkPhysicalDeviceProperties2 physDevProps = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
-            0,
-            {},
-    };
-    VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
-            0,
-            {},
-    };
-
-    if (protectedContent) {
-        physDevProps.pNext = &protMemProps;
-    }
-
-    vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
-    if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
-        BAIL("Could not find a Vulkan 1.1+ physical device");
-    }
-
-    if (physDevProps.properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) {
-        // TODO: b/326633110 - SkiaVK is not working correctly on swiftshader path.
-        BAIL("CPU implementations of Vulkan is not supported");
-    }
-
-    // Check for syncfd support. Bail if we cannot both import and export them.
-    VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
-            nullptr,
-            VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-    };
-    VkExternalSemaphoreProperties semProps = {
-            VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
-    };
-    vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
-
-    bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
-                                             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
-            (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
-            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
-            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-
-    if (!sufficientSemaphoreSyncFdSupport) {
-        BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
-             "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
-             "compatibleHandleTypes 0x%x (needed 0x%x) "
-             "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
-             semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-             semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-             semProps.externalSemaphoreFeatures,
-             VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
-                     VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-    } else {
-        ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
-              "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
-              "compatibleHandleTypes 0x%x (needed 0x%x) "
-              "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
-              semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-              semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
-              semProps.externalSemaphoreFeatures,
-              VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
-                      VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
-    }
-
-    uint32_t queueCount;
-    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
-    if (queueCount == 0) {
-        BAIL("Could not find queues for physical device");
-    }
-
-    std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
-    std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
-    VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
-    // Even though we don't yet know if the VK_EXT_global_priority extension is available,
-    // we can safely add the request to the pNext chain, and if the extension is not
-    // available, it will be ignored.
-    for (uint32_t i = 0; i < queueCount; ++i) {
-        queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
-        queuePriorityProps[i].pNext = nullptr;
-        queueProps[i].pNext = &queuePriorityProps[i];
-    }
-    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
-
-    int graphicsQueueIndex = -1;
-    for (uint32_t i = 0; i < queueCount; ++i) {
-        // Look at potential answers to the VK_EXT_global_priority query.  If answers were
-        // provided, we may adjust the queuePriority.
-        if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
-            for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
-                if (queuePriorityProps[i].priorities[j] > queuePriority) {
-                    queuePriority = queuePriorityProps[i].priorities[j];
-                }
-            }
-            if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
-                interface.isRealtimePriority = true;
-            }
-            graphicsQueueIndex = i;
-            break;
-        }
-    }
-
-    if (graphicsQueueIndex == -1) {
-        BAIL("Could not find a graphics queue family");
-    }
-
-    uint32_t deviceExtensionCount;
-    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
-                                                  nullptr));
-    std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
-    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
-                                                  deviceExtensions.data()));
-
-    std::vector<const char*> enabledDeviceExtensionNames;
-    enabledDeviceExtensionNames.reserve(deviceExtensions.size());
-    interface.deviceExtensionNames.reserve(deviceExtensions.size());
-    for (const auto& devExt : deviceExtensions) {
-        enabledDeviceExtensionNames.push_back(devExt.extensionName);
-        interface.deviceExtensionNames.push_back(devExt.extensionName);
-    }
-
-    interface.grExtensions.init(sGetProc, instance, physicalDevice,
-                                enabledInstanceExtensionNames.size(),
-                                enabledInstanceExtensionNames.data(),
-                                enabledDeviceExtensionNames.size(),
-                                enabledDeviceExtensionNames.data());
-
-    if (!interface.grExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
-        BAIL("Vulkan driver doesn't support external semaphore fd");
-    }
-
-    interface.physicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
-    interface.physicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-    interface.physicalDeviceFeatures2->pNext = nullptr;
-
-    interface.samplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
-    interface.samplerYcbcrConversionFeatures->sType =
-            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
-    interface.samplerYcbcrConversionFeatures->pNext = nullptr;
-
-    interface.physicalDeviceFeatures2->pNext = interface.samplerYcbcrConversionFeatures;
-    void** tailPnext = &interface.samplerYcbcrConversionFeatures->pNext;
-
-    if (protectedContent) {
-        interface.protectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
-        interface.protectedMemoryFeatures->sType =
-                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
-        interface.protectedMemoryFeatures->pNext = nullptr;
-        *tailPnext = interface.protectedMemoryFeatures;
-        tailPnext = &interface.protectedMemoryFeatures->pNext;
-    }
-
-    if (interface.grExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
-        interface.deviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
-        interface.deviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
-        interface.deviceFaultFeatures->pNext = nullptr;
-        *tailPnext = interface.deviceFaultFeatures;
-        tailPnext = &interface.deviceFaultFeatures->pNext;
-    }
-
-    vkGetPhysicalDeviceFeatures2(physicalDevice, interface.physicalDeviceFeatures2);
-    // Looks like this would slow things down and we can't depend on it on all platforms
-    interface.physicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
-
-    if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
-        BAIL("Protected memory not supported");
-    }
-
-    float queuePriorities[1] = {0.0f};
-    void* queueNextPtr = nullptr;
-
-    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
-            nullptr,
-            // If queue priority is supported, RE should always have realtime priority.
-            queuePriority,
-    };
-
-    if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
-        queueNextPtr = &queuePriorityCreateInfo;
-    }
-
-    VkDeviceQueueCreateFlags deviceQueueCreateFlags =
-            (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
-
-    const VkDeviceQueueCreateInfo queueInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
-            queueNextPtr,
-            deviceQueueCreateFlags,
-            (uint32_t)graphicsQueueIndex,
-            1,
-            queuePriorities,
-    };
-
-    const VkDeviceCreateInfo deviceInfo = {
-            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-            interface.physicalDeviceFeatures2,
-            0,
-            1,
-            &queueInfo,
-            0,
-            nullptr,
-            (uint32_t)enabledDeviceExtensionNames.size(),
-            enabledDeviceExtensionNames.data(),
-            nullptr,
-    };
-
-    ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
-    VkDevice device;
-    VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
-    ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
-
-    VkQueue graphicsQueue;
-    VK_GET_DEV_PROC(device, GetDeviceQueue2);
-    const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
-                                                 deviceQueueCreateFlags,
-                                                 (uint32_t)graphicsQueueIndex, 0};
-    vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
-
-    VK_GET_DEV_PROC(device, DeviceWaitIdle);
-    VK_GET_DEV_PROC(device, DestroyDevice);
-    interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle;
-    interface.funcs.vkDestroyDevice = vkDestroyDevice;
-
-    VK_GET_DEV_PROC(device, CreateSemaphore);
-    VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
-    VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
-    VK_GET_DEV_PROC(device, DestroySemaphore);
-    interface.funcs.vkCreateSemaphore = vkCreateSemaphore;
-    interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
-    interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
-    interface.funcs.vkDestroySemaphore = vkDestroySemaphore;
-
-    // At this point, everything's succeeded and we can continue
-    interface.initialized = true;
-    interface.instance = instance;
-    interface.physicalDevice = physicalDevice;
-    interface.device = device;
-    interface.queue = graphicsQueue;
-    interface.queueIndex = graphicsQueueIndex;
-    interface.apiVersion = physDevProps.properties.apiVersion;
-    // grExtensions already constructed
-    // feature pointers already constructed
-    interface.grGetProc = sGetProc;
-    interface.isProtected = protectedContent;
-    // funcs already initialized
-
-    const nsecs_t timeAfter = systemTime();
-    const float initTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
-    ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
-    return interface;
-}
-
-void teardownVulkanInterface(VulkanInterface* interface) {
-    interface->initialized = false;
-
-    if (interface->device != VK_NULL_HANDLE) {
-        interface->funcs.vkDeviceWaitIdle(interface->device);
-        interface->funcs.vkDestroyDevice(interface->device, nullptr);
-        interface->device = VK_NULL_HANDLE;
-    }
-    if (interface->instance != VK_NULL_HANDLE) {
-        interface->funcs.vkDestroyInstance(interface->instance, nullptr);
-        interface->instance = VK_NULL_HANDLE;
-    }
-
-    if (interface->protectedMemoryFeatures) {
-        delete interface->protectedMemoryFeatures;
-    }
-
-    if (interface->samplerYcbcrConversionFeatures) {
-        delete interface->samplerYcbcrConversionFeatures;
-    }
-
-    if (interface->physicalDeviceFeatures2) {
-        delete interface->physicalDeviceFeatures2;
-    }
-
-    if (interface->deviceFaultFeatures) {
-        delete interface->deviceFaultFeatures;
-    }
-
-    interface->samplerYcbcrConversionFeatures = nullptr;
-    interface->physicalDeviceFeatures2 = nullptr;
-    interface->protectedMemoryFeatures = nullptr;
-}
-
-static VulkanInterface sVulkanInterface;
-static VulkanInterface sProtectedContentVulkanInterface;
+static skia::VulkanInterface sVulkanInterface;
+static skia::VulkanInterface sProtectedContentVulkanInterface;
 
 static void sSetupVulkanInterface() {
-    if (!sVulkanInterface.initialized) {
-        sVulkanInterface = initVulkanInterface(false /* no protected content */);
+    if (!sVulkanInterface.isInitialized()) {
+        sVulkanInterface.init(false /* no protected content */);
         // We will have to abort if non-protected VkDevice creation fails (then nothing works).
-        LOG_ALWAYS_FATAL_IF(!sVulkanInterface.initialized,
+        LOG_ALWAYS_FATAL_IF(!sVulkanInterface.isInitialized(),
                             "Could not initialize Vulkan RenderEngine!");
     }
-    if (!sProtectedContentVulkanInterface.initialized) {
-        sProtectedContentVulkanInterface = initVulkanInterface(true /* protected content */);
-        if (!sProtectedContentVulkanInterface.initialized) {
+    if (!sProtectedContentVulkanInterface.isInitialized()) {
+        sProtectedContentVulkanInterface.init(true /* protected content */);
+        if (!sProtectedContentVulkanInterface.isInitialized()) {
             ALOGE("Could not initialize protected content Vulkan RenderEngine.");
         }
     }
@@ -667,12 +67,38 @@
         case GraphicsApi::GL:
             return true;
         case GraphicsApi::VK: {
-            if (!sVulkanInterface.initialized) {
-                sVulkanInterface = initVulkanInterface(false /* no protected content */);
-                ALOGD("%s: initialized == %s.", __func__,
-                      sVulkanInterface.initialized ? "true" : "false");
+            // Static local variables are initialized once, on first invocation of the function.
+            static const bool canSupportVulkan = []() {
+                if (!sVulkanInterface.isInitialized()) {
+                    sVulkanInterface.init(false /* no protected content */);
+                    ALOGD("%s: initialized == %s.", __func__,
+                          sVulkanInterface.isInitialized() ? "true" : "false");
+                    if (!sVulkanInterface.isInitialized()) {
+                        sVulkanInterface.teardown();
+                        return false;
+                    }
+                }
+                return true;
+            }();
+            return canSupportVulkan;
+        }
+    }
+}
+
+void RenderEngine::teardown(GraphicsApi graphicsApi) {
+    switch (graphicsApi) {
+        case GraphicsApi::GL:
+            break;
+        case GraphicsApi::VK: {
+            if (sVulkanInterface.isInitialized()) {
+                sVulkanInterface.teardown();
+                ALOGD("Tearing down the unprotected VulkanInterface.");
             }
-            return sVulkanInterface.initialized;
+            if (sProtectedContentVulkanInterface.isInitialized()) {
+                sProtectedContentVulkanInterface.teardown();
+                ALOGD("Tearing down the protected VulkanInterface.");
+            }
+            break;
         }
     }
 }
@@ -681,130 +107,61 @@
 
 using base::StringAppendF;
 
-std::unique_ptr<SkiaVkRenderEngine> SkiaVkRenderEngine::create(
-        const RenderEngineCreationArgs& args) {
-    std::unique_ptr<SkiaVkRenderEngine> engine(new SkiaVkRenderEngine(args));
-    engine->ensureGrContextsCreated();
-
-    if (sVulkanInterface.initialized) {
-        ALOGD("SkiaVkRenderEngine::%s: successfully initialized SkiaVkRenderEngine", __func__);
-        return engine;
-    } else {
-        ALOGD("SkiaVkRenderEngine::%s: could not create SkiaVkRenderEngine. "
-              "Likely insufficient Vulkan support",
-              __func__);
-        return {};
-    }
-}
-
 SkiaVkRenderEngine::SkiaVkRenderEngine(const RenderEngineCreationArgs& args)
       : SkiaRenderEngine(args.threaded, static_cast<PixelFormat>(args.pixelFormat),
-                         args.supportsBackgroundBlur) {}
+                         args.blurAlgorithm) {}
 
 SkiaVkRenderEngine::~SkiaVkRenderEngine() {
-    finishRenderingAndAbandonContext();
+    finishRenderingAndAbandonContexts();
+    // Teardown VulkanInterfaces after Skia contexts have been abandoned
+    teardown(GraphicsApi::VK);
 }
 
-SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
-        const GrContextOptions& options) {
+SkiaRenderEngine::Contexts SkiaVkRenderEngine::createContexts() {
     sSetupVulkanInterface();
+    // More work would need to be done in order to have multiple RenderEngine instances. In
+    // particular, they would not be able to share the same VulkanInterface(s).
+    LOG_ALWAYS_FATAL_IF(!sVulkanInterface.takeOwnership(),
+                        "SkiaVkRenderEngine couldn't take ownership of existing unprotected "
+                        "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                        "time.");
+    if (sProtectedContentVulkanInterface.isInitialized()) {
+        // takeOwnership fails on an uninitialized VulkanInterface, but protected content support is
+        // optional.
+        LOG_ALWAYS_FATAL_IF(!sProtectedContentVulkanInterface.takeOwnership(),
+                            "SkiaVkRenderEngine couldn't take ownership of existing protected "
+                            "VulkanInterface! Only one SkiaVkRenderEngine instance may exist at a "
+                            "time.");
+    }
 
     SkiaRenderEngine::Contexts contexts;
-    contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+    contexts.first = createContext(sVulkanInterface);
     if (supportsProtectedContentImpl()) {
-        contexts.second =
-                GrDirectContexts::MakeVulkan(sProtectedContentVulkanInterface.getBackendContext(),
-                                             options);
+        contexts.second = createContext(sProtectedContentVulkanInterface);
     }
 
     return contexts;
 }
 
 bool SkiaVkRenderEngine::supportsProtectedContentImpl() const {
-    return sProtectedContentVulkanInterface.initialized;
+    return sProtectedContentVulkanInterface.isInitialized();
 }
 
 bool SkiaVkRenderEngine::useProtectedContextImpl(GrProtected) {
     return true;
 }
 
-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) {
-    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(semaphore);
-    --info->mRefs;
-    if (!info->mRefs) {
-        sProtectedContentVulkanInterface.destroySemaphore(info->mSemaphore);
-        delete info;
-    }
-}
-
-static VulkanInterface& getVulkanInterface(bool protectedContext) {
+VulkanInterface& SkiaVkRenderEngine::getVulkanInterface(bool protectedContext) {
     if (protectedContext) {
         return sProtectedContentVulkanInterface;
     }
     return sVulkanInterface;
 }
 
-void SkiaVkRenderEngine::waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) {
-    if (fenceFd.get() < 0) return;
-
-    int dupedFd = dup(fenceFd.get());
-    if (dupedFd < 0) {
-        ALOGE("failed to create duplicate fence fd: %d", dupedFd);
-        sync_wait(fenceFd.get(), -1);
-        return;
-    }
-
-    base::unique_fd fenceDup(dupedFd);
-    VkSemaphore waitSemaphore =
-            getVulkanInterface(isProtected()).importSemaphoreFromSyncFd(fenceDup.release());
-    GrBackendSemaphore beSemaphore;
-    beSemaphore.initVulkan(waitSemaphore);
-    grContext->wait(1, &beSemaphore, true /* delete after wait */);
-}
-
-base::unique_fd SkiaVkRenderEngine::flushAndSubmit(GrDirectContext* grContext) {
-    VulkanInterface& vi = getVulkanInterface(isProtected());
-    VkSemaphore semaphore = vi.createExportableSemaphore();
-
-    GrBackendSemaphore backendSemaphore;
-    backendSemaphore.initVulkan(semaphore);
-
-    GrFlushInfo flushInfo;
-    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(GrSyncCpu::kNo);
-    int drawFenceFd = -1;
-    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;
-}
-
 int SkiaVkRenderEngine::getContextPriority() {
     // EGL_CONTEXT_PRIORITY_REALTIME_NV
     constexpr int kRealtimePriority = 0x3357;
-    if (getVulkanInterface(isProtected()).isRealtimePriority) {
+    if (getVulkanInterface(isProtected()).isRealtimePriority()) {
         return kRealtimePriority;
     } else {
         return 0;
@@ -813,21 +170,21 @@
 
 void SkiaVkRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
     StringAppendF(&result, "\n ------------RE Vulkan----------\n");
-    StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.initialized);
+    StringAppendF(&result, "\n Vulkan device initialized: %d\n", sVulkanInterface.isInitialized());
     StringAppendF(&result, "\n Vulkan protected device initialized: %d\n",
-                  sProtectedContentVulkanInterface.initialized);
+                  sProtectedContentVulkanInterface.isInitialized());
 
-    if (!sVulkanInterface.initialized) {
+    if (!sVulkanInterface.isInitialized()) {
         return;
     }
 
     StringAppendF(&result, "\n Instance extensions:\n");
-    for (const auto& name : sVulkanInterface.instanceExtensionNames) {
+    for (const auto& name : sVulkanInterface.getInstanceExtensionNames()) {
         StringAppendF(&result, "\n %s\n", name.c_str());
     }
 
     StringAppendF(&result, "\n Device extensions:\n");
-    for (const auto& name : sVulkanInterface.deviceExtensionNames) {
+    for (const auto& name : sVulkanInterface.getDeviceExtensionNames()) {
         StringAppendF(&result, "\n %s\n", name.c_str());
     }
 }
diff --git a/libs/renderengine/skia/SkiaVkRenderEngine.h b/libs/renderengine/skia/SkiaVkRenderEngine.h
index 52bc500..d2bb3d5 100644
--- a/libs/renderengine/skia/SkiaVkRenderEngine.h
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.h
@@ -17,9 +17,9 @@
 #ifndef SF_SKIAVKRENDERENGINE_H_
 #define SF_SKIAVKRENDERENGINE_H_
 
-#include <vk/GrVkBackendContext.h>
-
 #include "SkiaRenderEngine.h"
+#include "VulkanInterface.h"
+#include "compat/SkiaGpuContext.h"
 
 namespace android {
 namespace renderengine {
@@ -27,26 +27,64 @@
 
 class SkiaVkRenderEngine : public SkiaRenderEngine {
 public:
-    static std::unique_ptr<SkiaVkRenderEngine> create(const RenderEngineCreationArgs& args);
     ~SkiaVkRenderEngine() override;
 
     int getContextPriority() override;
 
+    class DestroySemaphoreInfo {
+    public:
+        DestroySemaphoreInfo() = delete;
+        DestroySemaphoreInfo(const DestroySemaphoreInfo&) = delete;
+        DestroySemaphoreInfo& operator=(const DestroySemaphoreInfo&) = delete;
+        DestroySemaphoreInfo& operator=(DestroySemaphoreInfo&&) = delete;
+
+        DestroySemaphoreInfo(VulkanInterface& vulkanInterface, std::vector<VkSemaphore> semaphores)
+              : mVulkanInterface(vulkanInterface), mSemaphores(std::move(semaphores)) {}
+        DestroySemaphoreInfo(VulkanInterface& vulkanInterface, VkSemaphore semaphore)
+              : DestroySemaphoreInfo(vulkanInterface, std::vector<VkSemaphore>(1, semaphore)) {}
+
+        void unref() {
+            --mRefs;
+            if (!mRefs) {
+                for (VkSemaphore semaphore : mSemaphores) {
+                    mVulkanInterface.destroySemaphore(semaphore);
+                }
+                delete this;
+            }
+        }
+
+    private:
+        ~DestroySemaphoreInfo() = default;
+
+        VulkanInterface& mVulkanInterface;
+        std::vector<VkSemaphore> mSemaphores;
+        // 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 unref() is called on this object. Skia will call unref() once it is done with
+        // the semaphore and the GPU has finished work on the semaphore. SkiaVkRenderEngine calls
+        // unref() after sending the semaphore to Skia and exporting it if need be.
+        int mRefs = 2;
+    };
+
 protected:
+    virtual std::unique_ptr<SkiaGpuContext> createContext(VulkanInterface& vulkanInterface) = 0;
+    // Redeclare parent functions that Ganesh vs. Graphite subclasses must implement.
+    virtual void waitFence(SkiaGpuContext* context, base::borrowed_fd fenceFd) override = 0;
+    virtual base::unique_fd flushAndSubmit(SkiaGpuContext* context,
+                                           sk_sp<SkSurface> dstSurface) override = 0;
+
+    SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
+
     // Implementations of abstract SkiaRenderEngine functions specific to
-    // rendering backend
-    virtual SkiaRenderEngine::Contexts createDirectContexts(const GrContextOptions& options);
+    // Vulkan, but shareable between Ganesh and Graphite.
+    SkiaRenderEngine::Contexts createContexts() override;
     bool supportsProtectedContentImpl() const override;
     bool useProtectedContextImpl(GrProtected isProtected) override;
-    void waitFence(GrDirectContext* grContext, base::borrowed_fd fenceFd) override;
-    base::unique_fd flushAndSubmit(GrDirectContext* context) override;
     void appendBackendSpecificInfoToDump(std::string& result) override;
 
-private:
-    SkiaVkRenderEngine(const RenderEngineCreationArgs& args);
-    base::unique_fd flush();
-
-    GrVkBackendContext mBackendContext;
+    // TODO: b/300533018 - refactor this to be non-static
+    static VulkanInterface& getVulkanInterface(bool protectedContext);
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/VulkanInterface.cpp b/libs/renderengine/skia/VulkanInterface.cpp
new file mode 100644
index 0000000..37b69f6
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.cpp
@@ -0,0 +1,609 @@
+/*
+ * Copyright 2024 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 "RenderEngine"
+
+#include "VulkanInterface.h"
+
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
+
+#include <log/log_main.h>
+#include <utils/Timers.h>
+
+#include <cinttypes>
+#include <sstream>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+VulkanBackendContext VulkanInterface::getGaneshBackendContext() {
+    return this->getGraphiteBackendContext();
+};
+
+VulkanBackendContext VulkanInterface::getGraphiteBackendContext() {
+    VulkanBackendContext backendContext;
+    backendContext.fInstance = mInstance;
+    backendContext.fPhysicalDevice = mPhysicalDevice;
+    backendContext.fDevice = mDevice;
+    backendContext.fQueue = mQueue;
+    backendContext.fGraphicsQueueIndex = mQueueIndex;
+    backendContext.fMaxAPIVersion = mApiVersion;
+    backendContext.fVkExtensions = &mVulkanExtensions;
+    backendContext.fDeviceFeatures2 = mPhysicalDeviceFeatures2;
+    backendContext.fGetProc = mGrGetProc;
+    backendContext.fProtectedContext = mIsProtected ? Protected::kYes : Protected::kNo;
+    backendContext.fDeviceLostContext = this; // VulkanInterface is long-lived
+    backendContext.fDeviceLostProc = onVkDeviceFault;
+    return backendContext;
+};
+
+VkSemaphore VulkanInterface::createExportableSemaphore() {
+    VkExportSemaphoreCreateInfo exportInfo;
+    exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+    exportInfo.pNext = nullptr;
+    exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+    VkSemaphoreCreateInfo semaphoreInfo;
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = &exportInfo;
+    semaphoreInfo.flags = 0;
+
+    VkSemaphore semaphore;
+    VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to create semaphore. err %d\n", __func__, err);
+        return VK_NULL_HANDLE;
+    }
+
+    return semaphore;
+}
+
+// syncFd cannot be <= 0
+VkSemaphore VulkanInterface::importSemaphoreFromSyncFd(int syncFd) {
+    VkSemaphoreCreateInfo semaphoreInfo;
+    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+    semaphoreInfo.pNext = nullptr;
+    semaphoreInfo.flags = 0;
+
+    VkSemaphore semaphore;
+    VkResult err = mFuncs.vkCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to create import semaphore", __func__);
+        return VK_NULL_HANDLE;
+    }
+
+    VkImportSemaphoreFdInfoKHR importInfo;
+    importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
+    importInfo.pNext = nullptr;
+    importInfo.semaphore = semaphore;
+    importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
+    importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+    importInfo.fd = syncFd;
+
+    err = mFuncs.vkImportSemaphoreFdKHR(mDevice, &importInfo);
+    if (VK_SUCCESS != err) {
+        mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+        ALOGE("%s: failed to import semaphore", __func__);
+        return VK_NULL_HANDLE;
+    }
+
+    return semaphore;
+}
+
+int VulkanInterface::exportSemaphoreSyncFd(VkSemaphore semaphore) {
+    int res;
+
+    VkSemaphoreGetFdInfoKHR getFdInfo;
+    getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+    getFdInfo.pNext = nullptr;
+    getFdInfo.semaphore = semaphore;
+    getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+    VkResult err = mFuncs.vkGetSemaphoreFdKHR(mDevice, &getFdInfo, &res);
+    if (VK_SUCCESS != err) {
+        ALOGE("%s: failed to export semaphore, err: %d", __func__, err);
+        return -1;
+    }
+    return res;
+}
+
+void VulkanInterface::destroySemaphore(VkSemaphore semaphore) {
+    mFuncs.vkDestroySemaphore(mDevice, semaphore, nullptr);
+}
+
+void VulkanInterface::onVkDeviceFault(void* callbackContext, const std::string& description,
+                                      const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                      const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                      const std::vector<std::byte>& vendorBinaryData) {
+    VulkanInterface* interface = static_cast<VulkanInterface*>(callbackContext);
+    const std::string protectedStr = interface->mIsProtected ? "protected" : "non-protected";
+    // The final crash string should contain as much differentiating info as possible, up to 1024
+    // bytes. As this final message is constructed, the same information is also dumped to the logs
+    // but in a more verbose format. Building the crash string is unsightly, so the clearer logging
+    // statement is always placed first to give context.
+    ALOGE("VK_ERROR_DEVICE_LOST (%s context): %s", protectedStr.c_str(), description.c_str());
+    std::stringstream crashMsg;
+    crashMsg << "VK_ERROR_DEVICE_LOST (" << protectedStr;
+
+    if (!addressInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultAddressInfoEXT:", addressInfos.size());
+        crashMsg << ", " << addressInfos.size() << " address info (";
+        for (VkDeviceFaultAddressInfoEXT addressInfo : addressInfos) {
+            ALOGE(" addressType:       %d", (int)addressInfo.addressType);
+            ALOGE("  reportedAddress:  %" PRIu64, addressInfo.reportedAddress);
+            ALOGE("  addressPrecision: %" PRIu64, addressInfo.addressPrecision);
+            crashMsg << addressInfo.addressType << ":" << addressInfo.reportedAddress << ":"
+                     << addressInfo.addressPrecision << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorInfos.empty()) {
+        ALOGE("%zu VkDeviceFaultVendorInfoEXT:", vendorInfos.size());
+        crashMsg << ", " << vendorInfos.size() << " vendor info (";
+        for (VkDeviceFaultVendorInfoEXT vendorInfo : vendorInfos) {
+            ALOGE(" description:      %s", vendorInfo.description);
+            ALOGE("  vendorFaultCode: %" PRIu64, vendorInfo.vendorFaultCode);
+            ALOGE("  vendorFaultData: %" PRIu64, vendorInfo.vendorFaultData);
+            // Omit descriptions for individual vendor info structs in the crash string, as the
+            // fault code and fault data fields should be enough for clustering, and the verbosity
+            // isn't worth it. Additionally, vendors may just set the general description field of
+            // the overall fault to the description of the first element in this list, and that
+            // overall description will be placed at the end of the crash string.
+            crashMsg << vendorInfo.vendorFaultCode << ":" << vendorInfo.vendorFaultData << ", ";
+        }
+        crashMsg.seekp(-2, crashMsg.cur); // Move back to overwrite trailing ", "
+        crashMsg << ")";
+    }
+
+    if (!vendorBinaryData.empty()) {
+        // TODO: b/322830575 - Log in base64, or dump directly to a file that gets put in bugreports
+        ALOGE("%zu bytes of vendor-specific binary data (please notify Android's Core Graphics"
+              " Stack team if you observe this message).",
+              vendorBinaryData.size());
+        crashMsg << ", " << vendorBinaryData.size() << " bytes binary";
+    }
+
+    crashMsg << "): " << description;
+    LOG_ALWAYS_FATAL("%s", crashMsg.str().c_str());
+};
+
+static skgpu::VulkanGetProc sGetProc = [](const char* proc_name,
+                                          VkInstance instance,
+                                          VkDevice device) {
+    if (device != VK_NULL_HANDLE) {
+        return vkGetDeviceProcAddr(device, proc_name);
+    }
+    return vkGetInstanceProcAddr(instance, proc_name);
+};
+
+#define BAIL(fmt, ...)                                          \
+    {                                                           \
+        ALOGE("%s: " fmt ", bailing", __func__, ##__VA_ARGS__); \
+        return;                                                 \
+    }
+
+#define CHECK_NONNULL(expr)       \
+    if ((expr) == nullptr) {      \
+        BAIL("[%s] null", #expr); \
+    }
+
+#define VK_CHECK(expr)                              \
+    if ((expr) != VK_SUCCESS) {                     \
+        BAIL("[%s] failed. err = %d", #expr, expr); \
+        return;                                     \
+    }
+
+#define VK_GET_PROC(F)                                                           \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_INST_PROC(instance, F)                                      \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetInstanceProcAddr(instance, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+#define VK_GET_DEV_PROC(device, F)                                     \
+    PFN_vk##F vk##F = (PFN_vk##F)vkGetDeviceProcAddr(device, "vk" #F); \
+    CHECK_NONNULL(vk##F)
+
+void VulkanInterface::init(bool protectedContent) {
+    if (isInitialized()) {
+        ALOGW("Called init on already initialized VulkanInterface");
+        return;
+    }
+
+    const nsecs_t timeBefore = systemTime();
+
+    VK_GET_PROC(EnumerateInstanceVersion);
+    uint32_t instanceVersion;
+    VK_CHECK(vkEnumerateInstanceVersion(&instanceVersion));
+
+    if (instanceVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Vulkan instance API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(instanceVersion), VK_VERSION_MINOR(instanceVersion),
+             VK_VERSION_PATCH(instanceVersion));
+    }
+
+    const VkApplicationInfo appInfo = {
+            VK_STRUCTURE_TYPE_APPLICATION_INFO, nullptr, "surfaceflinger", 0, "android platform", 0,
+            VK_MAKE_VERSION(1, 1, 0),
+    };
+
+    VK_GET_PROC(EnumerateInstanceExtensionProperties);
+
+    uint32_t extensionCount = 0;
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr));
+    std::vector<VkExtensionProperties> instanceExtensions(extensionCount);
+    VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
+                                                    instanceExtensions.data()));
+    std::vector<const char*> enabledInstanceExtensionNames;
+    enabledInstanceExtensionNames.reserve(instanceExtensions.size());
+    mInstanceExtensionNames.reserve(instanceExtensions.size());
+    for (const auto& instExt : instanceExtensions) {
+        enabledInstanceExtensionNames.push_back(instExt.extensionName);
+        mInstanceExtensionNames.push_back(instExt.extensionName);
+    }
+
+    const VkInstanceCreateInfo instanceCreateInfo = {
+            VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+            nullptr,
+            0,
+            &appInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledInstanceExtensionNames.size(),
+            enabledInstanceExtensionNames.data(),
+    };
+
+    VK_GET_PROC(CreateInstance);
+    VkInstance instance;
+    VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+    VK_GET_INST_PROC(instance, DestroyInstance);
+    mFuncs.vkDestroyInstance = vkDestroyInstance;
+    VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
+    VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
+    VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
+    VK_GET_INST_PROC(instance, CreateDevice);
+
+    uint32_t physdevCount;
+    VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
+    if (physdevCount == 0) {
+        BAIL("Could not find any physical devices");
+    }
+
+    physdevCount = 1;
+    VkPhysicalDevice physicalDevice;
+    VkResult enumeratePhysDevsErr =
+            vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
+    if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
+        BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
+             enumeratePhysDevsErr);
+    }
+
+    VkPhysicalDeviceProperties2 physDevProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+            0,
+            {},
+    };
+    VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
+            0,
+            {},
+    };
+
+    if (protectedContent) {
+        physDevProps.pNext = &protMemProps;
+    }
+
+    vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
+    const uint32_t physicalDeviceApiVersion = physDevProps.properties.apiVersion;
+    if (physicalDeviceApiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+        BAIL("Vulkan physical device API version %" PRIu32 ".%" PRIu32 ".%" PRIu32 " < 1.1.0",
+             VK_VERSION_MAJOR(physicalDeviceApiVersion), VK_VERSION_MINOR(physicalDeviceApiVersion),
+             VK_VERSION_PATCH(physicalDeviceApiVersion));
+    }
+
+    // Check for syncfd support. Bail if we cannot both import and export them.
+    VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+            nullptr,
+            VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+    };
+    VkExternalSemaphoreProperties semProps = {
+            VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
+    };
+    vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
+
+    bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
+                                             VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
+            (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
+    if (!sufficientSemaphoreSyncFdSupport) {
+        BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
+             "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+             "compatibleHandleTypes 0x%x (needed 0x%x) "
+             "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+             semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+             semProps.externalSemaphoreFeatures,
+             VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                     VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    } else {
+        ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
+              "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+              "compatibleHandleTypes 0x%x (needed 0x%x) "
+              "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+              semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+              semProps.externalSemaphoreFeatures,
+              VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+                      VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+    }
+
+    uint32_t queueCount;
+    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
+    if (queueCount == 0) {
+        BAIL("Could not find queues for physical device");
+    }
+
+    std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
+    std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
+    VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
+    // Even though we don't yet know if the VK_EXT_global_priority extension is available,
+    // we can safely add the request to the pNext chain, and if the extension is not
+    // available, it will be ignored.
+    for (uint32_t i = 0; i < queueCount; ++i) {
+        queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
+        queuePriorityProps[i].pNext = nullptr;
+        queueProps[i].pNext = &queuePriorityProps[i];
+    }
+    vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
+
+    int graphicsQueueIndex = -1;
+    for (uint32_t i = 0; i < queueCount; ++i) {
+        // Look at potential answers to the VK_EXT_global_priority query.  If answers were
+        // provided, we may adjust the queuePriority.
+        if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+            for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
+                if (queuePriorityProps[i].priorities[j] > queuePriority) {
+                    queuePriority = queuePriorityProps[i].priorities[j];
+                }
+            }
+            if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
+                mIsRealtimePriority = true;
+            }
+            graphicsQueueIndex = i;
+            break;
+        }
+    }
+
+    if (graphicsQueueIndex == -1) {
+        BAIL("Could not find a graphics queue family");
+    }
+
+    uint32_t deviceExtensionCount;
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  nullptr));
+    std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
+    VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+                                                  deviceExtensions.data()));
+
+    std::vector<const char*> enabledDeviceExtensionNames;
+    enabledDeviceExtensionNames.reserve(deviceExtensions.size());
+    mDeviceExtensionNames.reserve(deviceExtensions.size());
+    for (const auto& devExt : deviceExtensions) {
+        enabledDeviceExtensionNames.push_back(devExt.extensionName);
+        mDeviceExtensionNames.push_back(devExt.extensionName);
+    }
+
+    mVulkanExtensions.init(sGetProc, instance, physicalDevice, enabledInstanceExtensionNames.size(),
+                           enabledInstanceExtensionNames.data(), enabledDeviceExtensionNames.size(),
+                           enabledDeviceExtensionNames.data());
+
+    if (!mVulkanExtensions.hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
+        BAIL("Vulkan driver doesn't support external semaphore fd");
+    }
+
+    mPhysicalDeviceFeatures2 = new VkPhysicalDeviceFeatures2;
+    mPhysicalDeviceFeatures2->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+    mPhysicalDeviceFeatures2->pNext = nullptr;
+
+    mSamplerYcbcrConversionFeatures = new VkPhysicalDeviceSamplerYcbcrConversionFeatures;
+    mSamplerYcbcrConversionFeatures->sType =
+            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES;
+    mSamplerYcbcrConversionFeatures->pNext = nullptr;
+
+    mPhysicalDeviceFeatures2->pNext = mSamplerYcbcrConversionFeatures;
+    void** tailPnext = &mSamplerYcbcrConversionFeatures->pNext;
+
+    if (protectedContent) {
+        mProtectedMemoryFeatures = new VkPhysicalDeviceProtectedMemoryFeatures;
+        mProtectedMemoryFeatures->sType =
+                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
+        mProtectedMemoryFeatures->pNext = nullptr;
+        *tailPnext = mProtectedMemoryFeatures;
+        tailPnext = &mProtectedMemoryFeatures->pNext;
+    }
+
+    if (mVulkanExtensions.hasExtension(VK_EXT_DEVICE_FAULT_EXTENSION_NAME, 1)) {
+        mDeviceFaultFeatures = new VkPhysicalDeviceFaultFeaturesEXT;
+        mDeviceFaultFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FAULT_FEATURES_EXT;
+        mDeviceFaultFeatures->pNext = nullptr;
+        *tailPnext = mDeviceFaultFeatures;
+        tailPnext = &mDeviceFaultFeatures->pNext;
+    }
+
+    vkGetPhysicalDeviceFeatures2(physicalDevice, mPhysicalDeviceFeatures2);
+    // Looks like this would slow things down and we can't depend on it on all platforms
+    mPhysicalDeviceFeatures2->features.robustBufferAccess = VK_FALSE;
+
+    if (protectedContent && !mProtectedMemoryFeatures->protectedMemory) {
+        BAIL("Protected memory not supported");
+    }
+
+    float queuePriorities[1] = {0.0f};
+    void* queueNextPtr = nullptr;
+
+    VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
+            nullptr,
+            // If queue priority is supported, RE should always have realtime priority.
+            queuePriority,
+    };
+
+    if (mVulkanExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+        queueNextPtr = &queuePriorityCreateInfo;
+    }
+
+    VkDeviceQueueCreateFlags deviceQueueCreateFlags =
+            (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
+
+    const VkDeviceQueueCreateInfo queueInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+            queueNextPtr,
+            deviceQueueCreateFlags,
+            (uint32_t)graphicsQueueIndex,
+            1,
+            queuePriorities,
+    };
+
+    const VkDeviceCreateInfo deviceInfo = {
+            VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+            mPhysicalDeviceFeatures2,
+            0,
+            1,
+            &queueInfo,
+            0,
+            nullptr,
+            (uint32_t)enabledDeviceExtensionNames.size(),
+            enabledDeviceExtensionNames.data(),
+            nullptr,
+    };
+
+    ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
+    VkDevice device;
+    VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
+    ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
+
+    VkQueue graphicsQueue;
+    VK_GET_DEV_PROC(device, GetDeviceQueue2);
+    const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
+                                                 deviceQueueCreateFlags,
+                                                 (uint32_t)graphicsQueueIndex, 0};
+    vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
+
+    VK_GET_DEV_PROC(device, DeviceWaitIdle);
+    VK_GET_DEV_PROC(device, DestroyDevice);
+    mFuncs.vkDeviceWaitIdle = vkDeviceWaitIdle;
+    mFuncs.vkDestroyDevice = vkDestroyDevice;
+
+    VK_GET_DEV_PROC(device, CreateSemaphore);
+    VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
+    VK_GET_DEV_PROC(device, DestroySemaphore);
+    mFuncs.vkCreateSemaphore = vkCreateSemaphore;
+    mFuncs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
+    mFuncs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
+    mFuncs.vkDestroySemaphore = vkDestroySemaphore;
+
+    // At this point, everything's succeeded and we can continue
+    mInitialized = true;
+    mInstance = instance;
+    mPhysicalDevice = physicalDevice;
+    mDevice = device;
+    mQueue = graphicsQueue;
+    mQueueIndex = graphicsQueueIndex;
+    mApiVersion = physicalDeviceApiVersion;
+    // grExtensions already constructed
+    // feature pointers already constructed
+    mGrGetProc = sGetProc;
+    mIsProtected = protectedContent;
+    // mIsRealtimePriority already initialized by constructor
+    // funcs already initialized
+
+    const nsecs_t timeAfter = systemTime();
+    const float initTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
+    ALOGD("%s: Success init Vulkan interface in %f ms", __func__, initTimeMs);
+}
+
+bool VulkanInterface::takeOwnership() {
+    if (!isInitialized() || mIsOwned) {
+        return false;
+    }
+    mIsOwned = true;
+    return true;
+}
+
+void VulkanInterface::teardown() {
+    // Core resources that must be destroyed using Vulkan functions.
+    if (mDevice != VK_NULL_HANDLE) {
+        mFuncs.vkDeviceWaitIdle(mDevice);
+        mFuncs.vkDestroyDevice(mDevice, nullptr);
+        mDevice = VK_NULL_HANDLE;
+    }
+    if (mInstance != VK_NULL_HANDLE) {
+        mFuncs.vkDestroyInstance(mInstance, nullptr);
+        mInstance = VK_NULL_HANDLE;
+    }
+
+    // Optional features that can be deleted directly.
+    // TODO: b/293371537 - This section should likely be improved to walk the pNext chain of
+    // mPhysicalDeviceFeatures2 and free everything like HWUI's VulkanManager.
+    if (mProtectedMemoryFeatures) {
+        delete mProtectedMemoryFeatures;
+        mProtectedMemoryFeatures = nullptr;
+    }
+    if (mSamplerYcbcrConversionFeatures) {
+        delete mSamplerYcbcrConversionFeatures;
+        mSamplerYcbcrConversionFeatures = nullptr;
+    }
+    if (mPhysicalDeviceFeatures2) {
+        delete mPhysicalDeviceFeatures2;
+        mPhysicalDeviceFeatures2 = nullptr;
+    }
+    if (mDeviceFaultFeatures) {
+        delete mDeviceFaultFeatures;
+        mDeviceFaultFeatures = nullptr;
+    }
+
+    // Misc. fields that can be trivially reset without special deletion:
+    mInitialized = false;
+    mIsOwned = false;
+    mPhysicalDevice = VK_NULL_HANDLE; // Implicitly destroyed by destroying mInstance.
+    mQueue = VK_NULL_HANDLE;          // Implicitly destroyed by destroying mDevice.
+    mQueueIndex = 0;
+    mApiVersion = 0;
+    mVulkanExtensions = skgpu::VulkanExtensions();
+    mGrGetProc = nullptr;
+    mIsProtected = false;
+    mIsRealtimePriority = false;
+
+    mFuncs = VulkanFuncs();
+
+    mInstanceExtensionNames.clear();
+    mDeviceExtensionNames.clear();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/VulkanInterface.h b/libs/renderengine/skia/VulkanInterface.h
new file mode 100644
index 0000000..d0fe4d1
--- /dev/null
+++ b/libs/renderengine/skia/VulkanInterface.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2024 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 <include/gpu/vk/VulkanBackendContext.h>
+#include <include/gpu/vk/VulkanExtensions.h>
+#include <include/gpu/vk/VulkanTypes.h>
+
+#include <vulkan/vulkan.h>
+
+using namespace skgpu;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+class VulkanInterface {
+public:
+    // Create an uninitialized interface. Initialize with `init`.
+    VulkanInterface() = default;
+    ~VulkanInterface() = default;
+    VulkanInterface(const VulkanInterface&) = delete;
+    VulkanInterface& operator=(const VulkanInterface&) = delete;
+    VulkanInterface& operator=(VulkanInterface&&) = delete;
+
+    void init(bool protectedContent = false);
+    // Returns true and marks this VulkanInterface as "owned" if it is initialized but unused by any
+    // RenderEngine instances. Returns false if already owned, indicating that it must not be used
+    // by a new RE instance.
+    bool takeOwnership();
+    void teardown();
+
+    // TODO(b/309785258) Combine these into one now that they are the same implementation.
+    VulkanBackendContext getGaneshBackendContext();
+    VulkanBackendContext getGraphiteBackendContext();
+    VkSemaphore createExportableSemaphore();
+    VkSemaphore importSemaphoreFromSyncFd(int syncFd);
+    int exportSemaphoreSyncFd(VkSemaphore semaphore);
+    void destroySemaphore(VkSemaphore semaphore);
+
+    bool isInitialized() const { return mInitialized; }
+    bool isRealtimePriority() const { return mIsRealtimePriority; }
+    const std::vector<std::string>& getInstanceExtensionNames() { return mInstanceExtensionNames; }
+    const std::vector<std::string>& getDeviceExtensionNames() { return mDeviceExtensionNames; }
+
+private:
+    struct VulkanFuncs {
+        PFN_vkCreateSemaphore vkCreateSemaphore = nullptr;
+        PFN_vkImportSemaphoreFdKHR vkImportSemaphoreFdKHR = nullptr;
+        PFN_vkGetSemaphoreFdKHR vkGetSemaphoreFdKHR = nullptr;
+        PFN_vkDestroySemaphore vkDestroySemaphore = nullptr;
+
+        PFN_vkDeviceWaitIdle vkDeviceWaitIdle = nullptr;
+        PFN_vkDestroyDevice vkDestroyDevice = nullptr;
+        PFN_vkDestroyInstance vkDestroyInstance = nullptr;
+    };
+
+    static void onVkDeviceFault(void* callbackContext, const std::string& description,
+                                const std::vector<VkDeviceFaultAddressInfoEXT>& addressInfos,
+                                const std::vector<VkDeviceFaultVendorInfoEXT>& vendorInfos,
+                                const std::vector<std::byte>& vendorBinaryData);
+
+    // Note: keep all field defaults in sync with teardown()
+    bool mInitialized = false;
+    bool mIsOwned = false;
+    VkInstance mInstance = VK_NULL_HANDLE;
+    VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE;
+    VkDevice mDevice = VK_NULL_HANDLE;
+    VkQueue mQueue = VK_NULL_HANDLE;
+    int mQueueIndex = 0;
+    uint32_t mApiVersion = 0;
+    skgpu::VulkanExtensions mVulkanExtensions;
+    VkPhysicalDeviceFeatures2* mPhysicalDeviceFeatures2 = nullptr;
+    VkPhysicalDeviceSamplerYcbcrConversionFeatures* mSamplerYcbcrConversionFeatures = nullptr;
+    VkPhysicalDeviceProtectedMemoryFeatures* mProtectedMemoryFeatures = nullptr;
+    VkPhysicalDeviceFaultFeaturesEXT* mDeviceFaultFeatures = nullptr;
+    skgpu::VulkanGetProc mGrGetProc = nullptr;
+    bool mIsProtected = false;
+    bool mIsRealtimePriority = false;
+
+    VulkanFuncs mFuncs;
+
+    std::vector<std::string> mInstanceExtensionNames;
+    std::vector<std::string> mDeviceExtensionNames;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.cpp b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
new file mode 100644
index 0000000..88282e7
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.cpp
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2024 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 "GaneshBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <include/core/SkImage.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkTypes.h>
+
+#include "skia/ColorSpaces.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android/hardware_buffer.h>
+#include <common/trace.h>
+#include <log/log_main.h>
+
+namespace android::renderengine::skia {
+
+GaneshBackendTexture::GaneshBackendTexture(sk_sp<GrDirectContext> grContext,
+                                           AHardwareBuffer* buffer, bool isOutputBuffer)
+      : SkiaBackendTexture(buffer, isOutputBuffer), mGrContext(grContext) {
+    SFTRACE_CALL();
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+    const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+    GrBackendFormat backendFormat;
+    const GrBackendApi graphicsApi = grContext->backend();
+    if (graphicsApi == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(grContext.get(), desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(grContext.get(), buffer, desc.width,
+                                                             desc.height, &mDeleteProc,
+                                                             &mUpdateProc, &mImageCtx,
+                                                             createProtectedImage, backendFormat,
+                                                             isOutputBuffer);
+    } else if (graphicsApi == GrBackendApi::kVulkan) {
+        backendFormat = GrAHardwareBufferUtils::GetVulkanBackendFormat(grContext.get(), buffer,
+                                                                       desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(grContext.get(), buffer,
+                                                                 desc.width, desc.height,
+                                                                 &mDeleteProc, &mUpdateProc,
+                                                                 &mImageCtx, createProtectedImage,
+                                                                 backendFormat, isOutputBuffer);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected graphics API %u", static_cast<unsigned>(graphicsApi));
+    }
+
+    if (!mBackendTexture.isValid() || !desc.width || !desc.height) {
+        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+                         "isWriteable:%d format:%d",
+                         this, desc.width, desc.height, createProtectedImage, isOutputBuffer,
+                         desc.format);
+    }
+}
+
+GaneshBackendTexture::~GaneshBackendTexture() {
+    if (mBackendTexture.isValid()) {
+        mDeleteProc(mImageCtx);
+        mBackendTexture = {};
+    }
+}
+
+sk_sp<SkImage> GaneshBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                               TextureReleaseProc releaseImageProc,
+                                               ReleaseContext releaseContext) {
+    if (mBackendTexture.isValid()) {
+        mUpdateProc(mImageCtx, mGrContext.get());
+    }
+
+    const SkColorType colorType = colorTypeForImage(alphaType);
+    sk_sp<SkImage> image =
+            SkImages::BorrowTextureFrom(mGrContext.get(), mBackendTexture, kTopLeft_GrSurfaceOrigin,
+                                        colorType, alphaType, toSkColorSpace(dataspace),
+                                        releaseImageProc, releaseContext);
+    if (!image) {
+        logFatalTexture("Unable to generate SkImage.", dataspace, colorType);
+    }
+    return image;
+}
+
+sk_sp<SkSurface> GaneshBackendTexture::makeSurface(ui::Dataspace dataspace,
+                                                   TextureReleaseProc releaseSurfaceProc,
+                                                   ReleaseContext releaseContext) {
+    const SkColorType colorType = internalColorType();
+    sk_sp<SkSurface> surface =
+            SkSurfaces::WrapBackendTexture(mGrContext.get(), mBackendTexture,
+                                           kTopLeft_GrSurfaceOrigin, 0, colorType,
+                                           toSkColorSpace(dataspace), nullptr, releaseSurfaceProc,
+                                           releaseContext);
+    if (!surface) {
+        logFatalTexture("Unable to generate SkSurface.", dataspace, colorType);
+    }
+    return surface;
+}
+
+void GaneshBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace,
+                                           SkColorType colorType) {
+    switch (mBackendTexture.backend()) {
+        case GrBackendApi::kOpenGL: {
+            GrGLTextureInfo textureInfo;
+            bool retrievedTextureInfo =
+                    GrBackendTextures::GetGLTextureInfo(mBackendTexture, &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, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+                             mBackendTexture.width(), mBackendTexture.height(),
+                             mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+                             static_cast<int>(mBackendTexture.textureType()), retrievedTextureInfo,
+                             textureInfo.fTarget, textureInfo.fFormat, colorType);
+            break;
+        }
+        case GrBackendApi::kVulkan: {
+            GrVkImageInfo imageInfo;
+            bool retrievedImageInfo =
+                    GrBackendTextures::GetVkImageInfo(mBackendTexture, &imageInfo);
+            LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+                             "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+                             "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
+                             "fSampleCount: %u fLevelCount: %u colorType %i",
+                             msg, mBackendTexture.isValid(), static_cast<int32_t>(dataspace),
+                             mBackendTexture.width(), mBackendTexture.height(),
+                             mBackendTexture.hasMipmaps(), mBackendTexture.isProtected(),
+                             static_cast<int>(mBackendTexture.textureType()), retrievedImageInfo,
+                             imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
+                             colorType);
+            break;
+        }
+        default:
+            LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg,
+                             static_cast<unsigned>(mBackendTexture.backend()));
+            break;
+    }
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshBackendTexture.h b/libs/renderengine/skia/compat/GaneshBackendTexture.h
new file mode 100644
index 0000000..4337df1
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshBackendTexture.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 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 "SkiaBackendTexture.h"
+#include "ui/GraphicTypes.h"
+
+#include <include/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshBackendTexture : public SkiaBackendTexture {
+public:
+    // Creates an internal GrBackendTexture whose contents come from the provided buffer.
+    GaneshBackendTexture(sk_sp<GrDirectContext> grContext, AHardwareBuffer* buffer,
+                         bool isOutputBuffer);
+
+    ~GaneshBackendTexture() override;
+
+    sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                             TextureReleaseProc releaseImageProc,
+                             ReleaseContext releaseContext) override;
+
+    sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc,
+                                 ReleaseContext releaseContext) override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GaneshBackendTexture);
+
+    void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType);
+
+    const sk_sp<GrDirectContext> mGrContext;
+    GrBackendTexture mBackendTexture;
+    GrAHardwareBufferUtils::DeleteImageProc mDeleteProc;
+    GrAHardwareBufferUtils::UpdateImageProc mUpdateProc;
+    GrAHardwareBufferUtils::TexImageCtx mImageCtx;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.cpp b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
new file mode 100644
index 0000000..931f843
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.cpp
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2024 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 "GaneshGpuContext.h"
+
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/core/SkTraceMemoryDump.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+#include <include/gpu/ganesh/GrTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
+
+#include "../AutoBackendTexture.h"
+#include "GaneshBackendTexture.h"
+#include "skia/compat/SkiaBackendTexture.h"
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+#include <memory>
+
+namespace android::renderengine::skia {
+
+namespace {
+static GrContextOptions ganeshOptions(GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    GrContextOptions options;
+    options.fDisableDriverCorrectnessWorkarounds = true;
+    options.fDisableDistanceFieldPaths = true;
+    options.fReducedShaderVariations = true;
+    options.fPersistentCache = &skSLCacheMonitor;
+    return options;
+}
+} // namespace
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeGL_Ganesh(
+        sk_sp<const GrGLInterface> glInterface,
+        GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    return std::make_unique<GaneshGpuContext>(
+            GrDirectContexts::MakeGL(glInterface, ganeshOptions(skSLCacheMonitor)));
+}
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Ganesh(
+        const skgpu::VulkanBackendContext& vkBackendContext,
+        GrContextOptions::PersistentCache& skSLCacheMonitor) {
+    return std::make_unique<GaneshGpuContext>(
+            GrDirectContexts::MakeVulkan(vkBackendContext, ganeshOptions(skSLCacheMonitor)));
+}
+
+GaneshGpuContext::GaneshGpuContext(sk_sp<GrDirectContext> grContext) : mGrContext(grContext) {
+    LOG_ALWAYS_FATAL_IF(mGrContext.get() == nullptr, "GrDirectContext creation failed");
+}
+
+GaneshGpuContext::~GaneshGpuContext() {
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+    mGrContext->abandonContext();
+};
+
+sk_sp<GrDirectContext> GaneshGpuContext::grDirectContext() {
+    return mGrContext;
+}
+
+std::unique_ptr<SkiaBackendTexture> GaneshGpuContext::makeBackendTexture(AHardwareBuffer* buffer,
+                                                                         bool isOutputBuffer) {
+    return std::make_unique<GaneshBackendTexture>(mGrContext, buffer, isOutputBuffer);
+}
+
+sk_sp<SkSurface> GaneshGpuContext::createRenderTarget(SkImageInfo imageInfo) {
+    constexpr int kSampleCount = 1; // enable AA
+    constexpr SkSurfaceProps* kProps = nullptr;
+    constexpr bool kMipmapped = false;
+    return SkSurfaces::RenderTarget(mGrContext.get(), skgpu::Budgeted::kNo, imageInfo, kSampleCount,
+                                    kTopLeft_GrSurfaceOrigin, kProps, kMipmapped,
+                                    mGrContext->supportsProtectedContent());
+}
+
+size_t GaneshGpuContext::getMaxRenderTargetSize() const {
+    return mGrContext->maxRenderTargetSize();
+};
+
+size_t GaneshGpuContext::getMaxTextureSize() const {
+    return mGrContext->maxTextureSize();
+};
+
+bool GaneshGpuContext::isAbandonedOrDeviceLost() {
+    return mGrContext->abandoned();
+}
+
+void GaneshGpuContext::setResourceCacheLimit(size_t maxResourceBytes) {
+    mGrContext->setResourceCacheLimit(maxResourceBytes);
+}
+
+void GaneshGpuContext::purgeUnlockedScratchResources() {
+    mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kScratchResourcesOnly);
+}
+
+void GaneshGpuContext::resetContextIfApplicable() {
+    mGrContext->resetContext(); // Only applicable to GL
+};
+
+void GaneshGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
+    mGrContext->dumpMemoryStatistics(traceMemoryDump);
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GaneshGpuContext.h b/libs/renderengine/skia/compat/GaneshGpuContext.h
new file mode 100644
index 0000000..aeb1a82
--- /dev/null
+++ b/libs/renderengine/skia/compat/GaneshGpuContext.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 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 "SkiaGpuContext.h"
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GaneshGpuContext : public SkiaGpuContext {
+public:
+    GaneshGpuContext(sk_sp<GrDirectContext> grContext);
+    ~GaneshGpuContext() override;
+
+    sk_sp<GrDirectContext> grDirectContext() override;
+
+    std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                           bool isOutputBuffer) override;
+
+    sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override;
+
+    size_t getMaxRenderTargetSize() const override;
+    size_t getMaxTextureSize() const override;
+    bool isAbandonedOrDeviceLost() override;
+    void setResourceCacheLimit(size_t maxResourceBytes) override;
+
+    void purgeUnlockedScratchResources() override;
+    void resetContextIfApplicable() override;
+
+    void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GaneshGpuContext);
+
+    const sk_sp<GrDirectContext> mGrContext;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
new file mode 100644
index 0000000..a6e93ba
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 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 "GraphiteBackendTexture.h"
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <include/core/SkSurfaceProps.h>
+#include <include/gpu/graphite/Image.h>
+#include <include/gpu/graphite/Surface.h>
+#include <include/gpu/graphite/TextureInfo.h>
+
+#include "skia/ColorSpaces.h"
+
+#include <android/hardware_buffer.h>
+#include <common/trace.h>
+#include <inttypes.h>
+#include <log/log_main.h>
+
+namespace android::renderengine::skia {
+
+GraphiteBackendTexture::GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder,
+                                               AHardwareBuffer* buffer, bool isOutputBuffer)
+      : SkiaBackendTexture(buffer, isOutputBuffer), mRecorder(std::move(recorder)) {
+    SFTRACE_CALL();
+    AHardwareBuffer_Desc desc;
+    AHardwareBuffer_describe(buffer, &desc);
+    const bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
+
+    const SkISize dimensions = {static_cast<int32_t>(desc.width),
+                                static_cast<int32_t>(desc.height)};
+    LOG_ALWAYS_FATAL_IF(static_cast<uint32_t>(dimensions.width()) != desc.width ||
+                                static_cast<uint32_t>(dimensions.height()) != desc.height,
+                        "Failed to create a valid texture, casting unsigned dimensions [%" PRIu32
+                        ",%" PRIu32 "] to signed [%" PRIo32 ",%" PRIo32 "] "
+                        "is invalid",
+                        desc.width, desc.height, dimensions.width(), dimensions.height());
+
+    mBackendTexture = mRecorder->createBackendTexture(buffer, isOutputBuffer, createProtectedImage,
+                                                      dimensions, false);
+    if (!mBackendTexture.isValid() || !dimensions.width() || !dimensions.height()) {
+        LOG_ALWAYS_FATAL("Failed to create a valid texture. [%p]:[%d,%d] isProtected:%d "
+                         "isWriteable:%d format:%d",
+                         this, dimensions.width(), dimensions.height(), createProtectedImage,
+                         isOutputBuffer, desc.format);
+    }
+}
+
+GraphiteBackendTexture::~GraphiteBackendTexture() {
+    if (mBackendTexture.isValid()) {
+        mRecorder->deleteBackendTexture(mBackendTexture);
+        mBackendTexture = {};
+    }
+}
+
+sk_sp<SkImage> GraphiteBackendTexture::makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                                 TextureReleaseProc releaseImageProc,
+                                                 ReleaseContext releaseContext) {
+    const SkColorType colorType = colorTypeForImage(alphaType);
+    sk_sp<SkImage> image =
+            SkImages::WrapTexture(mRecorder.get(), mBackendTexture, colorType, alphaType,
+                                  toSkColorSpace(dataspace), releaseImageProc, releaseContext);
+    if (!image) {
+        logFatalTexture("Unable to generate SkImage.", dataspace, colorType);
+    }
+    return image;
+}
+
+sk_sp<SkSurface> GraphiteBackendTexture::makeSurface(ui::Dataspace dataspace,
+                                                     TextureReleaseProc releaseSurfaceProc,
+                                                     ReleaseContext releaseContext) {
+    const SkColorType colorType = internalColorType();
+    SkSurfaceProps props;
+    sk_sp<SkSurface> surface =
+            SkSurfaces::WrapBackendTexture(mRecorder.get(), mBackendTexture, colorType,
+                                           toSkColorSpace(dataspace), &props, releaseSurfaceProc,
+                                           releaseContext);
+    if (!surface) {
+        logFatalTexture("Unable to generate SkSurface.", dataspace, colorType);
+    }
+    return surface;
+}
+
+void GraphiteBackendTexture::logFatalTexture(const char* msg, ui::Dataspace dataspace,
+                                             SkColorType colorType) {
+    // TODO: b/293371537 - Iterate on this logging (validate failure cases, possibly check
+    // VulkanTextureInfo, etc.)
+    const skgpu::graphite::TextureInfo& textureInfo = mBackendTexture.info();
+    LOG_ALWAYS_FATAL("%s isOutputBuffer:%d, dataspace:%d, colorType:%d"
+                     "\n\tBackendTexture: isValid:%d, dimensions:%dx%d"
+                     "\n\t\tTextureInfo: isValid:%d, numSamples:%d, mipmapped:%d, isProtected: %d",
+                     msg, isOutputBuffer(), static_cast<int32_t>(dataspace), colorType,
+                     mBackendTexture.isValid(), mBackendTexture.dimensions().width(),
+                     mBackendTexture.dimensions().height(), textureInfo.isValid(),
+                     textureInfo.numSamples(), static_cast<int32_t>(textureInfo.mipmapped()),
+                     static_cast<int32_t>(textureInfo.isProtected()));
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteBackendTexture.h b/libs/renderengine/skia/compat/GraphiteBackendTexture.h
new file mode 100644
index 0000000..3bec3f7
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteBackendTexture.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 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 "SkiaBackendTexture.h"
+
+#include <include/core/SkColorSpace.h>
+#include <include/core/SkImage.h>
+#include <include/core/SkSurface.h>
+#include <include/gpu/graphite/BackendTexture.h>
+#include <include/gpu/graphite/Recorder.h>
+
+#include <android-base/macros.h>
+#include <ui/GraphicTypes.h>
+
+#include <memory>
+
+namespace android::renderengine::skia {
+
+class GraphiteBackendTexture : public SkiaBackendTexture {
+public:
+    // Creates an internal skgpu::graphite::BackendTexture whose contents come from the provided
+    // buffer.
+    GraphiteBackendTexture(std::shared_ptr<skgpu::graphite::Recorder> recorder,
+                           AHardwareBuffer* buffer, bool isOutputBuffer);
+
+    ~GraphiteBackendTexture() override;
+
+    sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                             TextureReleaseProc releaseImageProc,
+                             ReleaseContext releaseContext) override;
+
+    sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace, TextureReleaseProc releaseSurfaceProc,
+                                 ReleaseContext releaseContext) override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GraphiteBackendTexture);
+
+    void logFatalTexture(const char* msg, ui::Dataspace dataspace, SkColorType colorType);
+
+    const std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
+    skgpu::graphite::BackendTexture mBackendTexture;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.cpp b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
new file mode 100644
index 0000000..69f5832
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2024 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 "GraphiteGpuContext.h"
+
+#include <include/core/SkImageInfo.h>
+#include <include/core/SkSurface.h>
+#include <include/core/SkTraceMemoryDump.h>
+#include <include/gpu/graphite/GraphiteTypes.h>
+#include <include/gpu/graphite/Surface.h>
+#include <include/gpu/graphite/vk/VulkanGraphiteUtils.h>
+
+#include "GpuTypes.h"
+#include "skia/compat/GraphiteBackendTexture.h"
+
+#include <android-base/macros.h>
+#include <log/log_main.h>
+#include <memory>
+
+namespace android::renderengine::skia {
+
+namespace {
+static skgpu::graphite::ContextOptions graphiteOptions() {
+    skgpu::graphite::ContextOptions options;
+    options.fDisableDriverCorrectnessWorkarounds = true;
+    return options;
+}
+} // namespace
+
+std::unique_ptr<SkiaGpuContext> SkiaGpuContext::MakeVulkan_Graphite(
+        const skgpu::VulkanBackendContext& vulkanBackendContext) {
+    return std::make_unique<GraphiteGpuContext>(
+            skgpu::graphite::ContextFactory::MakeVulkan(vulkanBackendContext, graphiteOptions()));
+}
+
+GraphiteGpuContext::GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context)
+      : mContext(std::move(context)) {
+    LOG_ALWAYS_FATAL_IF(mContext.get() == nullptr, "graphite::Context creation failed");
+    LOG_ALWAYS_FATAL_IF(mContext->backend() != skgpu::BackendApi::kVulkan,
+                        "graphite::Context::backend() == %d, but GraphiteBackendContext makes "
+                        "assumptions that are only valid for Vulkan (%d)",
+                        static_cast<int>(mContext->backend()),
+                        static_cast<int>(skgpu::BackendApi::kVulkan));
+
+    // TODO: b/293371537 - Iterate on default cache limits (the Recorder should have the majority of
+    // the budget, and the Context should be given a smaller fraction.)
+    skgpu::graphite::RecorderOptions recorderOptions = skgpu::graphite::RecorderOptions();
+    this->mRecorder = mContext->makeRecorder(recorderOptions);
+    LOG_ALWAYS_FATAL_IF(mRecorder.get() == nullptr, "graphite::Recorder creation failed");
+}
+
+GraphiteGpuContext::~GraphiteGpuContext() {
+    // The equivalent operation would occur when destroying the graphite::Context, but calling this
+    // explicitly allows any outstanding GraphiteBackendTextures to be released, thus allowing us to
+    // assert that this GraphiteGpuContext holds the last ref to the underlying graphite::Recorder.
+    mContext->submit(skgpu::graphite::SyncToCpu::kYes);
+    // We must call the Context's and Recorder's dtors before exiting this function, so all other
+    // refs must be released by now. Note: these assertions may be unreliable in a hypothetical
+    // future world where we take advantage of Graphite's multi-threading capabilities!
+    LOG_ALWAYS_FATAL_IF(mRecorder.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Recorder");
+    LOG_ALWAYS_FATAL_IF(mContext.use_count() > 1,
+                        "Something other than GraphiteGpuContext holds a ref to the underlying "
+                        "graphite::Context");
+};
+
+std::shared_ptr<skgpu::graphite::Context> GraphiteGpuContext::graphiteContext() {
+    return mContext;
+}
+
+std::shared_ptr<skgpu::graphite::Recorder> GraphiteGpuContext::graphiteRecorder() {
+    return mRecorder;
+}
+
+std::unique_ptr<SkiaBackendTexture> GraphiteGpuContext::makeBackendTexture(AHardwareBuffer* buffer,
+                                                                           bool isOutputBuffer) {
+    return std::make_unique<GraphiteBackendTexture>(graphiteRecorder(), buffer, isOutputBuffer);
+}
+
+sk_sp<SkSurface> GraphiteGpuContext::createRenderTarget(SkImageInfo imageInfo) {
+    constexpr SkSurfaceProps* kProps = nullptr;
+    return SkSurfaces::RenderTarget(mRecorder.get(), imageInfo, skgpu::Mipmapped::kNo, kProps);
+}
+
+size_t GraphiteGpuContext::getMaxRenderTargetSize() const {
+    // maxRenderTargetSize only differs from maxTextureSize on GL, so as long as Graphite implies
+    // Vk, then the distinction is irrelevant.
+    return getMaxTextureSize();
+};
+
+size_t GraphiteGpuContext::getMaxTextureSize() const {
+    return mContext->maxTextureSize();
+};
+
+bool GraphiteGpuContext::isAbandonedOrDeviceLost() {
+    return mContext->isDeviceLost();
+}
+
+void GraphiteGpuContext::dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const {
+    mContext->dumpMemoryStatistics(traceMemoryDump);
+}
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/GraphiteGpuContext.h b/libs/renderengine/skia/compat/GraphiteGpuContext.h
new file mode 100644
index 0000000..413817f
--- /dev/null
+++ b/libs/renderengine/skia/compat/GraphiteGpuContext.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 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 "SkiaGpuContext.h"
+#include "graphite/Recorder.h"
+
+#include <android-base/macros.h>
+
+namespace android::renderengine::skia {
+
+class GraphiteGpuContext : public SkiaGpuContext {
+public:
+    GraphiteGpuContext(std::unique_ptr<skgpu::graphite::Context> context);
+    ~GraphiteGpuContext() override;
+
+    std::shared_ptr<skgpu::graphite::Context> graphiteContext() override;
+    std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() override;
+
+    std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                           bool isOutputBuffer) override;
+
+    sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) override;
+
+    size_t getMaxRenderTargetSize() const override;
+    size_t getMaxTextureSize() const override;
+    bool isAbandonedOrDeviceLost() override;
+    // No-op (large resources like textures, surfaces, images, etc. created by clients don't count
+    // towards Graphite's internal caching budgets, so adjusting its limits based on display change
+    // events should be unnecessary. Additionally, Graphite doesn't expose many cache tweaking
+    // functions yet, as its design may evolve.)
+    void setResourceCacheLimit(size_t maxResourceBytes) override{};
+
+    // TODO: b/293371537 - Triple-check and validate that no cleanup is necessary when switching
+    // contexts.
+    // No-op (unnecessary during context switch for Graphite's client-budgeted memory model).
+    void purgeUnlockedScratchResources() override{};
+    // No-op (only applicable to GL).
+    void resetContextIfApplicable() override{};
+
+    void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const override;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GraphiteGpuContext);
+
+    std::shared_ptr<skgpu::graphite::Context> mContext;
+    std::shared_ptr<skgpu::graphite::Recorder> mRecorder;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/SkiaBackendTexture.h b/libs/renderengine/skia/compat/SkiaBackendTexture.h
new file mode 100644
index 0000000..fa12624
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaBackendTexture.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 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 <include/android/GrAHardwareBufferUtils.h>
+#include <include/core/SkColorSpace.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+
+#include <android/hardware_buffer.h>
+#include <ui/GraphicTypes.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over a Skia backend-specific texture type.
+ *
+ * This class does not do any lifecycle management, and should typically be wrapped in an
+ * AutoBackendTexture::LocalRef. Typically created via SkiaGpuContext::makeBackendTexture(...).
+ */
+class SkiaBackendTexture {
+public:
+    SkiaBackendTexture(AHardwareBuffer* buffer, bool isOutputBuffer)
+          : mIsOutputBuffer(isOutputBuffer) {
+        AHardwareBuffer_Desc desc;
+        AHardwareBuffer_describe(buffer, &desc);
+
+        mColorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+    }
+    virtual ~SkiaBackendTexture() = default;
+
+    // These two definitions mirror Skia's own types used for texture release callbacks, which are
+    // re-declared multiple times between context-specific implementation headers for Ganesh vs.
+    // Graphite, and within the context of SkImages vs. SkSurfaces. Our own re-declaration allows us
+    // to not pull in any implementation-specific headers here.
+    using ReleaseContext = void*;
+    using TextureReleaseProc = void (*)(ReleaseContext);
+
+    // Guaranteed to be non-null (crashes otherwise). An opaque alphaType may coerce the internal
+    // color type to RBGX.
+    virtual sk_sp<SkImage> makeImage(SkAlphaType alphaType, ui::Dataspace dataspace,
+                                     TextureReleaseProc releaseImageProc,
+                                     ReleaseContext releaseContext) = 0;
+
+    // Guaranteed to be non-null (crashes otherwise).
+    virtual sk_sp<SkSurface> makeSurface(ui::Dataspace dataspace,
+                                         TextureReleaseProc releaseSurfaceProc,
+                                         ReleaseContext releaseContext) = 0;
+
+    bool isOutputBuffer() const { return mIsOutputBuffer; }
+
+    SkColorType internalColorType() const { return mColorType; }
+
+protected:
+    // Strip alpha channel from rawColorType if alphaType is opaque (note: only works for RGBA_8888)
+    SkColorType colorTypeForImage(SkAlphaType alphaType) const {
+        if (alphaType == kOpaque_SkAlphaType) {
+            // TODO: b/40043126 - Support RGBX SkColorType for F16 and support it and 101010x as a
+            // source
+            if (internalColorType() == kRGBA_8888_SkColorType) {
+                return kRGB_888x_SkColorType;
+            }
+        }
+        return internalColorType();
+    }
+
+private:
+    const bool mIsOutputBuffer;
+    SkColorType mColorType = kUnknown_SkColorType;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/compat/SkiaGpuContext.h b/libs/renderengine/skia/compat/SkiaGpuContext.h
new file mode 100644
index 0000000..0bd8283
--- /dev/null
+++ b/libs/renderengine/skia/compat/SkiaGpuContext.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 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
+
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+
+#include <include/core/SkSurface.h>
+#include <include/gpu/ganesh/GrDirectContext.h>
+#include <include/gpu/ganesh/gl/GrGLInterface.h>
+#include <include/gpu/graphite/Context.h>
+#include <include/gpu/vk/VulkanBackendContext.h>
+
+#include "SkiaBackendTexture.h"
+
+#include <log/log.h>
+
+#include <memory>
+
+namespace android::renderengine::skia {
+
+/**
+ * Abstraction over Ganesh and Graphite's underlying context-like objects.
+ *
+ * On destruction, subclasses will submit any pending work before destroying their internal Skia
+ * context(s). Any unused cached SkiaBackendTextures created from a SkiaGpuContext that are awaiting
+ * cleanup must be deleted before destroying that SkiaGpuContext, and any textures that are released
+ * during ~SkiaGpuContext must be configured to be deleted immediately.
+ */
+class SkiaGpuContext {
+public:
+    /**
+     * glInterface must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeGL_Ganesh(
+            sk_sp<const GrGLInterface> glInterface,
+            GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+    /**
+     * vkBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeVulkan_Ganesh(
+            const skgpu::VulkanBackendContext& vkBackendContext,
+            GrContextOptions::PersistentCache& skSLCacheMonitor);
+
+    // TODO: b/293371537 - Need shader / pipeline monitoring support in Graphite.
+    /**
+     * vulkanBackendContext must remain valid until after SkiaGpuContext is destroyed.
+     */
+    static std::unique_ptr<SkiaGpuContext> MakeVulkan_Graphite(
+            const skgpu::VulkanBackendContext& vulkanBackendContext);
+
+    virtual ~SkiaGpuContext() = default;
+
+    /**
+     * Only callable on Ganesh-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual sk_sp<GrDirectContext> grDirectContext() {
+        LOG_ALWAYS_FATAL("grDirectContext() called on a non-Ganesh instance of SkiaGpuContext!");
+    }
+
+    /**
+     * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual std::shared_ptr<skgpu::graphite::Context> graphiteContext() {
+        LOG_ALWAYS_FATAL("graphiteContext() called on a non-Graphite instance of SkiaGpuContext!");
+    }
+
+    /**
+     * Only callable on Graphite-backed instances of SkiaGpuContext, otherwise fatal.
+     */
+    virtual std::shared_ptr<skgpu::graphite::Recorder> graphiteRecorder() {
+        LOG_ALWAYS_FATAL("graphiteRecorder() called on a non-Graphite instance of SkiaGpuContext!");
+    }
+
+    virtual std::unique_ptr<SkiaBackendTexture> makeBackendTexture(AHardwareBuffer* buffer,
+                                                                   bool isOutputBuffer) = 0;
+
+    /**
+     * Notes:
+     * - The surface doesn't count against Skia's caching budgets.
+     * - Protected status is set to match the implementation's underlying context.
+     * - The origin of the surface in texture space corresponds to the top-left content pixel.
+     * - AA is always enabled.
+     */
+    virtual sk_sp<SkSurface> createRenderTarget(SkImageInfo imageInfo) = 0;
+
+    virtual bool isAbandonedOrDeviceLost() = 0;
+    virtual size_t getMaxRenderTargetSize() const = 0;
+    virtual size_t getMaxTextureSize() const = 0;
+    virtual void setResourceCacheLimit(size_t maxResourceBytes) = 0;
+
+    virtual void purgeUnlockedScratchResources() = 0;
+    virtual void resetContextIfApplicable() = 0; // No-op outside of GL (&& Ganesh at this point.)
+
+    virtual void dumpMemoryStatistics(SkTraceMemoryDump* traceMemoryDump) const = 0;
+};
+
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/debug/CaptureTimer.cpp b/libs/renderengine/skia/debug/CaptureTimer.cpp
index 11bcdb8..1c1ee0a 100644
--- a/libs/renderengine/skia/debug/CaptureTimer.cpp
+++ b/libs/renderengine/skia/debug/CaptureTimer.cpp
@@ -30,7 +30,7 @@
 
 void CaptureTimer::setTimeout(TimeoutCallback function, std::chrono::milliseconds delay) {
     this->clear = false;
-    CommonPool::post([=]() {
+    CommonPool::post([=,this]() {
         if (this->clear) return;
         std::this_thread::sleep_for(delay);
         if (this->clear) return;
diff --git a/libs/renderengine/skia/debug/CommonPool.cpp b/libs/renderengine/skia/debug/CommonPool.cpp
index bf15300..9d7c69b 100644
--- a/libs/renderengine/skia/debug/CommonPool.cpp
+++ b/libs/renderengine/skia/debug/CommonPool.cpp
@@ -20,8 +20,8 @@
 #define LOG_TAG "RenderEngine"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <sys/resource.h>
-#include <utils/Trace.h>
 
 #include <system/thread_defs.h>
 #include <array>
@@ -31,7 +31,7 @@
 namespace skia {
 
 CommonPool::CommonPool() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     CommonPool* pool = this;
     // Create 2 workers
diff --git a/libs/renderengine/skia/debug/SkiaCapture.cpp b/libs/renderengine/skia/debug/SkiaCapture.cpp
index 48dc77e..e6a0e22 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -22,15 +22,15 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <renderengine/RenderEngine.h>
-#include <utils/Trace.h>
 
 #include "CommonPool.h"
 #include "SkCanvas.h"
 #include "SkRect.h"
 #include "SkTypeface.h"
-#include "src/utils/SkMultiPictureDocument.h"
+#include "include/docs/SkMultiPictureDocument.h"
 #include <sys/stat.h>
 
 namespace android {
@@ -48,7 +48,7 @@
 }
 
 SkCanvas* SkiaCapture::tryCapture(SkSurface* surface) NO_THREAD_SAFETY_ANALYSIS {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // If we are not running yet, set up.
     if (CC_LIKELY(!mCaptureRunning)) {
@@ -86,7 +86,7 @@
 }
 
 void SkiaCapture::endCapture() NO_THREAD_SAFETY_ANALYSIS {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Don't end anything if we are not running.
     if (CC_LIKELY(!mCaptureRunning)) {
         return;
@@ -102,7 +102,7 @@
 }
 
 SkCanvas* SkiaCapture::tryOffscreenCapture(SkSurface* surface, OffscreenState* state) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Don't start anything if we are not running.
     if (CC_LIKELY(!mCaptureRunning)) {
         return surface->getCanvas();
@@ -122,7 +122,7 @@
 }
 
 uint64_t SkiaCapture::endOffscreenCapture(OffscreenState* state) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Don't end anything if we are not running.
     if (CC_LIKELY(!mCaptureRunning)) {
         return 0;
@@ -151,7 +151,7 @@
 }
 
 void SkiaCapture::writeToFile() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Pass mMultiPic and mOpenMultiPicStream to a background thread, which will
     // handle the heavyweight serialization work and destroy them.
     // mOpenMultiPicStream is released to a bare pointer because keeping it in
@@ -169,7 +169,7 @@
 }
 
 bool SkiaCapture::setupMultiFrameCapture() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
     base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
 
@@ -196,7 +196,7 @@
         // procs doesn't need to outlive this Make call
         // The last argument is a callback for the endPage behavior.
         // See SkSharingProc.h for more explanation of this callback.
-        mMultiPic = SkMakeMultiPictureDocument(
+        mMultiPic = SkMultiPictureDocument::Make(
                 mOpenMultiPicStream.get(), &procs,
                 [sharingCtx = mSerialContext.get()](const SkPicture* pic) {
                     SkSharingSerialContext::collectNonTextureImagesFromPicture(pic, sharingCtx);
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 1e0c4cf..cd1bd71 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -25,8 +25,8 @@
 #include <SkString.h>
 #include <SkSurface.h>
 #include <SkTileMode.h>
+#include <common/trace.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 namespace android {
 namespace renderengine {
@@ -79,7 +79,7 @@
                                 const uint32_t blurRadius, const float blurAlpha,
                                 const SkRect& blurRect, sk_sp<SkImage> blurredImage,
                                 sk_sp<SkImage> input) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     SkPaint paint;
     paint.setAlphaf(blurAlpha);
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index 9cddc75..180c922 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -21,6 +21,8 @@
 #include <SkRuntimeEffect.h>
 #include <SkSurface.h>
 
+#include "../compat/SkiaGpuContext.h"
+
 using namespace std;
 
 namespace android {
@@ -38,8 +40,9 @@
     virtual ~BlurFilter(){}
 
     // Execute blur, saving it to a texture
-    virtual sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
-                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const = 0;
+    virtual sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
+                                    const sk_sp<SkImage> blurInput,
+                                    const SkRect& blurRect) const = 0;
 
     /**
      * Draw the blurred content (from the generate method) into the canvas.
diff --git a/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp
new file mode 100644
index 0000000..4164c4b
--- /dev/null
+++ b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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 "EdgeExtensionShaderFactory.h"
+#include <SkPoint.h>
+#include <SkRuntimeEffect.h>
+#include <SkStream.h>
+#include <SkString.h>
+#include <com_android_graphics_libgui_flags.h>
+#include "log/log_main.h"
+
+namespace android::renderengine::skia {
+
+static const SkString edgeShader = SkString(R"(
+    uniform shader uContentTexture;
+    uniform vec2 uImgSize;
+
+    // TODO(b/214232209) oobTolerance is temporary and will be removed when the scrollbar will be
+    // hidden during the animation
+    const float oobTolerance = 15;
+    const int blurRadius = 3;
+    const float blurArea = float((2 * blurRadius + 1) * (2 * blurRadius + 1));
+
+    vec4 boxBlur(vec2 p) {
+        vec4 sumColors = vec4(0);
+
+        for (int i = -blurRadius; i <= blurRadius; i++) {
+            for (int j = -blurRadius; j <= blurRadius; j++) {
+                sumColors += uContentTexture.eval(p + vec2(i, j));
+            }
+        }
+        return sumColors / blurArea;
+    }
+
+    vec4 main(vec2 coord) {
+        vec2 nearestTexturePoint = clamp(coord, vec2(0, 0), uImgSize);
+        if (coord == nearestTexturePoint) {
+            return uContentTexture.eval(coord);
+        } else {
+            vec2 samplePoint = nearestTexturePoint + oobTolerance * normalize(
+                                    nearestTexturePoint - coord);
+            return boxBlur(samplePoint);
+        }
+    }
+)");
+
+EdgeExtensionShaderFactory::EdgeExtensionShaderFactory() {
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        return;
+    }
+    mResult = std::make_unique<SkRuntimeEffect::Result>(SkRuntimeEffect::MakeForShader(edgeShader));
+    LOG_ALWAYS_FATAL_IF(!mResult->errorText.isEmpty(),
+                        "EdgeExtensionShaderFactory compilation "
+                        "failed with an unexpected error: %s",
+                        mResult->errorText.c_str());
+}
+
+sk_sp<SkShader> EdgeExtensionShaderFactory::createSkShader(const sk_sp<SkShader>& inputShader,
+                                                           const LayerSettings& layer,
+                                                           const SkRect& imageBounds) const {
+    LOG_ALWAYS_FATAL_IF(mResult == nullptr,
+                        "EdgeExtensionShaderFactory did not initialize mResult. "
+                        "This means that we unexpectedly applied the edge extension shader");
+
+    SkRuntimeShaderBuilder builder = SkRuntimeShaderBuilder(mResult->effect);
+
+    builder.child("uContentTexture") = inputShader;
+    if (imageBounds.isEmpty()) {
+        builder.uniform("uImgSize") = SkPoint{layer.geometry.boundaries.getWidth(),
+                                              layer.geometry.boundaries.getHeight()};
+    } else {
+        builder.uniform("uImgSize") = SkPoint{imageBounds.width(), imageBounds.height()};
+    }
+    return builder.makeShader();
+}
+} // namespace android::renderengine::skia
\ No newline at end of file
diff --git a/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h
new file mode 100644
index 0000000..17c6b91
--- /dev/null
+++ b/libs/renderengine/skia/filters/EdgeExtensionShaderFactory.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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 <SkImage.h>
+#include <SkRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include <renderengine/LayerSettings.h>
+#include <ui/EdgeExtensionEffect.h>
+
+namespace android::renderengine::skia {
+
+/**
+ * This shader is designed to prolong the texture of a surface whose bounds have been extended over
+ * the size of the texture. This shader is similar to the default clamp, but adds a blur effect and
+ * samples from close to the edge (compared to on the edge) to avoid weird artifacts when elements
+ * (in particular, scrollbars) touch the edge.
+ */
+class EdgeExtensionShaderFactory {
+public:
+    EdgeExtensionShaderFactory();
+
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& inputShader, const LayerSettings& layer,
+                                   const SkRect& imageBounds) const;
+
+private:
+    std::unique_ptr<const SkRuntimeEffect::Result> mResult;
+};
+} // namespace android::renderengine::skia
diff --git a/libs/renderengine/skia/filters/GainmapFactory.cpp b/libs/renderengine/skia/filters/GainmapFactory.cpp
new file mode 100644
index 0000000..e4d4fe9
--- /dev/null
+++ b/libs/renderengine/skia/filters/GainmapFactory.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 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 "GainmapFactory.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+namespace {
+
+sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) {
+    auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl);
+    LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str());
+    return effect;
+}
+
+// Please refer to https://developer.android.com/media/platform/hdr-image-format#gain_map-generation
+static const SkString kGainmapShader = SkString(R"(
+    uniform shader sdr;
+    uniform shader hdr;
+    uniform float mapMaxLog2;
+
+    const float mapMinLog2 = 0.0;
+    const float mapGamma = 1.0;
+    const float offsetSdr = 0.015625;
+    const float offsetHdr = 0.015625;
+
+    float luminance(vec3 linearColor) {
+        return 0.2126 * linearColor.r + 0.7152 * linearColor.g + 0.0722 * linearColor.b;
+    }
+
+    vec4 main(vec2 xy) {
+        float sdrY = luminance(toLinearSrgb(sdr.eval(xy).rgb));
+        float hdrY = luminance(toLinearSrgb(hdr.eval(xy).rgb));
+        float pixelGain = (hdrY + offsetHdr) / (sdrY + offsetSdr);
+        float logRecovery = (log2(pixelGain) - mapMinLog2) / (mapMaxLog2 - mapMinLog2);
+        return vec4(pow(clamp(logRecovery, 0.0, 1.0), mapGamma));
+    }
+)");
+} // namespace
+
+const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
+
+GainmapFactory::GainmapFactory() : mEffect(makeEffect(kGainmapShader)) {}
+
+sk_sp<SkShader> GainmapFactory::createSkShader(const sk_sp<SkShader>& sdr,
+                                               const sk_sp<SkShader>& hdr, float hdrSdrRatio) {
+    SkRuntimeShaderBuilder shaderBuilder(mEffect);
+    shaderBuilder.child("sdr") = sdr;
+    shaderBuilder.child("hdr") = hdr;
+    shaderBuilder.uniform("mapMaxLog2") = std::log2(hdrSdrRatio);
+    return shaderBuilder.makeShader();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/GainmapFactory.h b/libs/renderengine/skia/filters/GainmapFactory.h
new file mode 100644
index 0000000..7aea5e2
--- /dev/null
+++ b/libs/renderengine/skia/filters/GainmapFactory.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 <SkRuntimeEffect.h>
+#include <SkShader.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * Generates a shader for computing a gainmap, given an SDR base image and its idealized HDR
+ * rendition. The shader follows the procedure in the UltraHDR spec:
+ * https://developer.android.com/media/platform/hdr-image-format#gain_map-generation, but makes some
+ * simplifying assumptions about metadata typical for RenderEngine's usage.
+ */
+class GainmapFactory {
+public:
+    GainmapFactory();
+    // Generates the gainmap shader. The hdrSdrRatio is the max_content_boost in the UltraHDR
+    // specification.
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& sdr, const sk_sp<SkShader>& hdr,
+                                   float hdrSdrRatio);
+
+private:
+    sk_sp<SkRuntimeEffect> mEffect;
+};
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index e72c501..8c52c57 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -19,18 +19,18 @@
 #include "GaussianBlurFilter.h"
 #include <SkBlendMode.h>
 #include <SkCanvas.h>
+#include <SkImageFilters.h>
 #include <SkPaint.h>
 #include <SkRRect.h>
 #include <SkRuntimeEffect.h>
-#include <SkImageFilters.h>
 #include <SkSize.h>
 #include <SkString.h>
 #include <SkSurface.h>
 #include <SkTileMode.h>
+#include <common/trace.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
-#include "include/gpu/GpuTypes.h" // from Skia
 #include <log/log.h>
-#include <utils/Trace.h>
+#include "include/gpu/GpuTypes.h" // from Skia
 
 namespace android {
 namespace renderengine {
@@ -42,14 +42,13 @@
 
 GaussianBlurFilter::GaussianBlurFilter(): BlurFilter(/* maxCrossFadeRadius= */ 0.0f) {}
 
-sk_sp<SkImage> GaussianBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
-                                            const sk_sp<SkImage> input, const SkRect& blurRect)
-    const {
+sk_sp<SkImage> GaussianBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
+                                            const sk_sp<SkImage> input,
+                                            const SkRect& blurRect) const {
     // Create blur surface with the bit depth and colorspace of the original surface
     SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
                                                        std::ceil(blurRect.height() * kInputScale));
-    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context,
-                                                        skgpu::Budgeted::kNo, scaledInfo);
+    sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.h b/libs/renderengine/skia/filters/GaussianBlurFilter.h
index a4febd2..878ab21 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.h
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.h
@@ -37,9 +37,8 @@
     virtual ~GaussianBlurFilter(){}
 
     // Execute blur, saving it to a texture
-    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
-
 };
 
 } // namespace skia
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
new file mode 100644
index 0000000..db0b133
--- /dev/null
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "KawaseBlurDualFilter.h"
+#include <SkAlphaType.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkData.h>
+#include <SkPaint.h>
+#include <SkRRect.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include <SkSize.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+KawaseBlurDualFilter::KawaseBlurDualFilter() : BlurFilter() {
+    // A shader to sample each vertex of a unit regular heptagon
+    // plus the original fragment coordinate.
+    SkString blurString(R"(
+        uniform shader child;
+        uniform float in_blurOffset;
+        uniform float in_crossFade;
+
+        const float2 STEP_0 = float2( 1.0, 0.0);
+        const float2 STEP_1 = float2( 0.623489802,  0.781831482);
+        const float2 STEP_2 = float2(-0.222520934,  0.974927912);
+        const float2 STEP_3 = float2(-0.900968868,  0.433883739);
+        const float2 STEP_4 = float2( 0.900968868, -0.433883739);
+        const float2 STEP_5 = float2(-0.222520934, -0.974927912);
+        const float2 STEP_6 = float2(-0.623489802, -0.781831482);
+
+        half4 main(float2 xy) {
+            half3 c = child.eval(xy).rgb;
+
+            c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_4 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
+            c += child.eval(xy + STEP_6 * in_blurOffset).rgb;
+
+            return half4(c * 0.125 * in_crossFade, in_crossFade);
+        }
+    )");
+
+    auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
+    LOG_ALWAYS_FATAL_IF(!blurEffect, "RuntimeShader error: %s", error.c_str());
+    mBlurEffect = std::move(blurEffect);
+}
+
+static sk_sp<SkSurface> makeSurface(SkiaGpuContext* context, const SkRect& origRect, int scale) {
+    SkImageInfo scaledInfo =
+            SkImageInfo::MakeN32Premul(ceil(static_cast<float>(origRect.width()) / scale),
+                                       ceil(static_cast<float>(origRect.height()) / scale));
+    return context->createRenderTarget(scaledInfo);
+}
+
+void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface,
+                                    const sk_sp<SkImage>& readImage, const float radius,
+                                    const float alpha) const {
+    const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
+    SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
+    blurInto(drawSurface,
+             readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                   SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
+                                   blurMatrix),
+             readImage->width() / static_cast<float>(drawSurface->width()), radius, alpha);
+}
+
+void KawaseBlurDualFilter::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
+                                    const float inverseScale, const float radius,
+                                    const float alpha) const {
+    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+    blurBuilder.child("child") = std::move(input);
+    blurBuilder.uniform("in_inverseScale") = inverseScale;
+    blurBuilder.uniform("in_blurOffset") = radius;
+    blurBuilder.uniform("in_crossFade") = alpha;
+    SkPaint paint;
+    paint.setShader(blurBuilder.makeShader(nullptr));
+    paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
+    drawSurface->getCanvas()->drawPaint(paint);
+}
+
+sk_sp<SkImage> KawaseBlurDualFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
+                                              const sk_sp<SkImage> input,
+                                              const SkRect& blurRect) const {
+    // Apply a conversion factor of (1 / sqrt(3)) to match Skia's built-in blur as used by
+    // RenderEffect. See the comment in SkBlurMask.cpp for reasoning behind this.
+    const float radius = blurRadius * 0.57735f;
+
+    // Use a variable number of blur passes depending on the radius. The non-integer part of this
+    // calculation is used to mix the final pass into the second-last with an alpha blend.
+    constexpr int kMaxSurfaces = 4;
+    const float filterDepth =
+            std::min(kMaxSurfaces - 1.0f, 1.0f + std::max(0.0f, log2f(radius * kInputScale)));
+    const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));
+
+    // Render into surfaces downscaled by 1x, 1x, 2x, and 4x from the initial downscale.
+    sk_sp<SkSurface> surfaces[kMaxSurfaces] =
+            {filterPasses >= 0 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
+             filterPasses >= 1 ? makeSurface(context, blurRect, 1 * kInverseInputScale) : nullptr,
+             filterPasses >= 2 ? makeSurface(context, blurRect, 2 * kInverseInputScale) : nullptr,
+             filterPasses >= 3 ? makeSurface(context, blurRect, 4 * kInverseInputScale) : nullptr};
+
+    // These weights for scaling offsets per-pass are handpicked to look good at 1 <= radius <= 600.
+    static const float kWeights[7] = {1.0f, 2.0f, 3.5f, 1.0f, 2.0f, 2.0f, 2.0f};
+
+    // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
+    // simpler blurs. A transformation is required to approximate the same effect as Gaussian.
+    float sumSquaredR = powf(kWeights[0] * powf(2.0f, 1), 2.0f);
+    for (int i = 0; i < filterPasses; i++) {
+        const float alpha = std::min(1.0f, filterDepth - i);
+        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[1 + i], 2.0f);
+        sumSquaredR += powf(powf(2.0f, i + 1) * alpha * kWeights[6 - i], 2.0f);
+    }
+    // Solve for R = sqrt(sum(r_i^2)). Divide R by hypot(1,1) to find some (x,y) offsets.
+    const float step = M_SQRT1_2 *
+            sqrtf(max(0.0f, (powf(radius, 2.0f) - powf(kInverseInputScale, 2.0f)) / sumSquaredR));
+
+    // Start by downscaling and doing the first blur pass.
+    {
+        // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
+        // case one may expect Translate(blurRect.fLeft, blurRect.fTop) * Scale(kInverseInputScale)
+        // but instead we must do the inverse.
+        SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
+        blurMatrix.postScale(kInputScale, kInputScale);
+        const auto sourceShader =
+                input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                  SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
+                                  blurMatrix);
+        blurInto(surfaces[0], std::move(sourceShader), kInputScale, kWeights[0] * step, 1.0f);
+    }
+    // Next the remaining downscale blur passes.
+    for (int i = 0; i < filterPasses; i++) {
+        blurInto(surfaces[i + 1], surfaces[i]->makeImageSnapshot(), kWeights[1 + i] * step, 1.0f);
+    }
+    // Finally blur+upscale back to our original size.
+    for (int i = filterPasses - 1; i >= 0; i--) {
+        blurInto(surfaces[i], surfaces[i + 1]->makeImageSnapshot(), kWeights[6 - i] * step,
+                 std::min(1.0f, filterDepth - i));
+    }
+    return surfaces[0]->makeImageSnapshot();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/KawaseBlurDualFilter.h b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
new file mode 100644
index 0000000..6f4adbf
--- /dev/null
+++ b/libs/renderengine/skia/filters/KawaseBlurDualFilter.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 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 <SkCanvas.h>
+#include <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+#include "BlurFilter.h"
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * This is an implementation of a Kawase blur with dual-filtering passes, as described in here:
+ * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_slides.pdf
+ * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+ */
+class KawaseBlurDualFilter : public BlurFilter {
+public:
+    explicit KawaseBlurDualFilter();
+    virtual ~KawaseBlurDualFilter() {}
+
+    // Execute blur, saving it to a texture
+    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
+                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
+
+private:
+    sk_sp<SkRuntimeEffect> mBlurEffect;
+
+    void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkImage>& readImage,
+                  const float radius, const float alpha) const;
+
+    void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkShader> input,
+                  const float inverseScale, const float radius, const float alpha) const;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
index 09f09a6..defaf6e 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -29,10 +29,10 @@
 #include <SkString.h>
 #include <SkSurface.h>
 #include <SkTileMode.h>
+#include <common/trace.h>
 #include <include/gpu/GpuTypes.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 namespace android {
 namespace renderengine {
@@ -73,8 +73,7 @@
     return surface->makeImageSnapshot();
 }
 
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
-                                          const uint32_t blurRadius,
+sk_sp<SkImage> KawaseBlurFilter::generate(SkiaGpuContext* context, const uint32_t blurRadius,
                                           const sk_sp<SkImage> input,
                                           const SkRect& blurRect) const {
     LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
@@ -108,12 +107,7 @@
             input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
     blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
 
-    constexpr int kSampleCount = 1;
-    constexpr bool kMipmapped = false;
-    constexpr SkSurfaceProps* kProps = nullptr;
-    sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kNo, scaledInfo,
-                                                        kSampleCount, kTopLeft_GrSurfaceOrigin,
-                                                        kProps, kMipmapped, input->isProtected());
+    sk_sp<SkSurface> surface = context->createRenderTarget(scaledInfo);
     LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
     sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
 
diff --git a/libs/renderengine/skia/filters/KawaseBlurFilter.h b/libs/renderengine/skia/filters/KawaseBlurFilter.h
index 0ac5ac8..429a537 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.h
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.h
@@ -42,7 +42,7 @@
     virtual ~KawaseBlurFilter(){}
 
     // Execute blur, saving it to a texture
-    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
+    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
                             const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;
 
 private:
diff --git a/libs/renderengine/skia/filters/LinearEffect.cpp b/libs/renderengine/skia/filters/LinearEffect.cpp
index f7dcd3a..3bc3564 100644
--- a/libs/renderengine/skia/filters/LinearEffect.cpp
+++ b/libs/renderengine/skia/filters/LinearEffect.cpp
@@ -19,9 +19,9 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <SkString.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <shaders/shaders.h>
-#include <utils/Trace.h>
 
 #include <math/mat4.h>
 
@@ -30,7 +30,7 @@
 namespace skia {
 
 sk_sp<SkRuntimeEffect> buildRuntimeEffect(const shaders::LinearEffect& linearEffect) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     SkString shaderString = SkString(shaders::buildLinearEffectSkSL(linearEffect));
 
     auto [shader, error] = SkRuntimeEffect::MakeForShader(shaderString);
@@ -45,7 +45,7 @@
         sk_sp<SkRuntimeEffect> runtimeEffect, const mat4& colorTransform, float maxDisplayLuminance,
         float currentDisplayLuminanceNits, float maxLuminance, AHardwareBuffer* buffer,
         aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     SkRuntimeShaderBuilder effectBuilder(runtimeEffect);
 
     effectBuilder.child("child") = shader;
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
new file mode 100644
index 0000000..b099bcf
--- /dev/null
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright 2024 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 "MouriMap.h"
+#include <SkCanvas.h>
+#include <SkColorType.h>
+#include <SkPaint.h>
+#include <SkTileMode.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+namespace {
+sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) {
+    auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl);
+    LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str());
+    return effect;
+}
+const SkString kCrosstalkAndChunk16x16(R"(
+    uniform shader bitmap;
+    uniform float hdrSdrRatio;
+    vec4 main(vec2 xy) {
+        float maximum = 0.0;
+        for (int y = 0; y < 16; y++) {
+            for (int x = 0; x < 16; x++) {
+                float3 linear = toLinearSrgb(bitmap.eval((xy - 0.5) * 16 + 0.5 + vec2(x, y)).rgb) * hdrSdrRatio;
+                float maxRGB = max(linear.r, max(linear.g, linear.b));
+                maximum = max(maximum, log2(max(maxRGB, 1.0)));
+            }
+        }
+        return float4(float3(maximum), 1.0);
+    }
+)");
+const SkString kChunk8x8(R"(
+    uniform shader bitmap;
+    vec4 main(vec2 xy) {
+        float maximum = 0.0;
+        for (int y = 0; y < 8; y++) {
+            for (int x = 0; x < 8; x++) {
+                maximum = max(maximum, bitmap.eval((xy - 0.5) * 8 + 0.5 + vec2(x, y)).r);
+            }
+        }
+        return float4(float3(maximum), 1.0);
+    }
+)");
+const SkString kBlur(R"(
+    uniform shader bitmap;
+    vec4 main(vec2 xy) {
+        float C[5];
+        C[0] = 1.0 / 16.0;
+        C[1] = 4.0 / 16.0;
+        C[2] = 6.0 / 16.0;
+        C[3] = 4.0 / 16.0;
+        C[4] = 1.0 / 16.0;
+        float result = 0.0;
+        for (int y = -2; y <= 2; y++) {
+            for (int x = -2; x <= 2; x++) {
+                result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r;
+            }
+        }
+        return float4(float3(exp2(result)), 1.0);
+    }
+)");
+const SkString kTonemap(R"(
+    uniform shader image;
+    uniform shader lux;
+    uniform float scaleFactor;
+    uniform float hdrSdrRatio;
+    uniform float targetHdrSdrRatio;
+    vec4 main(vec2 xy) {
+        float localMax = lux.eval(xy * scaleFactor).r;
+        float4 rgba = image.eval(xy);
+        float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
+
+        if (localMax <= targetHdrSdrRatio) {
+            return float4(fromLinearSrgb(linear), rgba.a);
+        }
+
+        float maxRGB = max(linear.r, max(linear.g, linear.b));
+        localMax = max(localMax, maxRGB);
+        float gain = (1 + maxRGB * (targetHdrSdrRatio / (localMax * localMax)))
+                / (1 + maxRGB / targetHdrSdrRatio);
+        return float4(fromLinearSrgb(linear * gain), rgba.a);
+    }
+)");
+
+// Draws the given runtime shader on a GPU surface and returns the result as an SkImage.
+sk_sp<SkImage> makeImage(SkSurface* surface, const SkRuntimeShaderBuilder& builder) {
+    sk_sp<SkShader> shader = builder.makeShader(nullptr);
+    LOG_ALWAYS_FATAL_IF(!shader, "%s, Failed to make shader!", __func__);
+    SkPaint paint;
+    paint.setShader(std::move(shader));
+    paint.setBlendMode(SkBlendMode::kSrc);
+    surface->getCanvas()->drawPaint(paint);
+    return surface->makeImageSnapshot();
+}
+
+} // namespace
+
+MouriMap::MouriMap()
+      : mCrosstalkAndChunk16x16(makeEffect(kCrosstalkAndChunk16x16)),
+        mChunk8x8(makeEffect(kChunk8x8)),
+        mBlur(makeEffect(kBlur)),
+        mTonemap(makeEffect(kTonemap)) {}
+
+sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
+                                   float hdrSdrRatio, float targetHdrSdrRatio) {
+    auto downchunked = downchunk(context, input, hdrSdrRatio);
+    auto localLux = blur(context, downchunked.get());
+    return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
+}
+
+sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
+                                   float hdrSdrRatio) const {
+    SkMatrix matrix;
+    SkImage* image = input->isAImage(&matrix, (SkTileMode*)nullptr);
+    SkRuntimeShaderBuilder crosstalkAndChunk16x16Builder(mCrosstalkAndChunk16x16);
+    crosstalkAndChunk16x16Builder.child("bitmap") = input;
+    crosstalkAndChunk16x16Builder.uniform("hdrSdrRatio") = hdrSdrRatio;
+    // TODO: fp16 might be overkill. Most practical surfaces use 8-bit RGB for HDR UI and 10-bit YUV
+    // for HDR video. These downsample operations compute log2(max(linear RGB, 1.0)). So we don't
+    // care about LDR precision since they all resolve to LDR-max. For appropriately mastered HDR
+    // content that follows BT. 2408, 25% of the bit range for HLG and 42% of the bit range for PQ
+    // are reserved for HDR. This means that we can fit the entire HDR range for 10-bit HLG inside
+    // of 8 bits. We can also fit about half of the range for PQ, but most content does not fill the
+    // entire 10k nit range for PQ. Furthermore, we blur all of this later on anyways, so we might
+    // not need to be so precise. So, it's possible that we could use A8 or R8 instead. If we want
+    // to be really conservative we can try to use R16 or even RGBA1010102 to fake an R10 surface,
+    // which would cut write bandwidth significantly.
+    static constexpr auto kFirstDownscaleAmount = 16;
+    sk_sp<SkSurface> firstDownsampledSurface = context->createRenderTarget(
+            image->imageInfo()
+                    .makeWH(std::max(1, image->width() / kFirstDownscaleAmount),
+                            std::max(1, image->height() / kFirstDownscaleAmount))
+                    .makeColorType(kRGBA_F16_SkColorType));
+    LOG_ALWAYS_FATAL_IF(!firstDownsampledSurface, "%s: Failed to create surface!", __func__);
+    auto firstDownsampledImage =
+            makeImage(firstDownsampledSurface.get(), crosstalkAndChunk16x16Builder);
+    SkRuntimeShaderBuilder chunk8x8Builder(mChunk8x8);
+    chunk8x8Builder.child("bitmap") =
+            firstDownsampledImage->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                                 SkSamplingOptions());
+    static constexpr auto kSecondDownscaleAmount = 8;
+    sk_sp<SkSurface> secondDownsampledSurface = context->createRenderTarget(
+            firstDownsampledImage->imageInfo()
+                    .makeWH(std::max(1, firstDownsampledImage->width() / kSecondDownscaleAmount),
+                            std::max(1, firstDownsampledImage->height() / kSecondDownscaleAmount)));
+    LOG_ALWAYS_FATAL_IF(!secondDownsampledSurface, "%s: Failed to create surface!", __func__);
+    return makeImage(secondDownsampledSurface.get(), chunk8x8Builder);
+}
+sk_sp<SkImage> MouriMap::blur(SkiaGpuContext* context, SkImage* input) const {
+    SkRuntimeShaderBuilder blurBuilder(mBlur);
+    blurBuilder.child("bitmap") =
+            input->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp, SkSamplingOptions());
+    sk_sp<SkSurface> blurSurface = context->createRenderTarget(input->imageInfo());
+    LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
+    return makeImage(blurSurface.get(), blurBuilder);
+}
+sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+                                  float targetHdrSdrRatio) const {
+    static constexpr float kScaleFactor = 1.0f / 128.0f;
+    SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
+    tonemapBuilder.child("image") = input;
+    tonemapBuilder.child("lux") =
+            localLux->makeRawShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                    SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
+    tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
+    tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
+    tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
+    return tonemapBuilder.makeShader();
+}
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h
new file mode 100644
index 0000000..9ba2b6f
--- /dev/null
+++ b/libs/renderengine/skia/filters/MouriMap.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 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 <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkShader.h>
+#include "../compat/SkiaGpuContext.h"
+namespace android {
+namespace renderengine {
+namespace skia {
+/**
+ * MouriMap is a fast, albeit not realtime, tonemapping algorithm optimized for near-exact
+ * preservation of SDR (or, equivalently, LDR) regions, while trying to do an acceptable job of
+ * preserving HDR detail.
+ *
+ * MouriMap is a local tonemapping algorithm, meaning that nearby pixels are taken into
+ * consideration when choosing a tonemapping curve.
+ *
+ * The algorithm conceptually is as follows:
+ * 1. Partition the image into 128x128 chunks, computing the log2(maximum luminance) in each chunk
+ *.    a. Maximum luminance is computed as max(R, G, B), where the R, G, B values are in linear
+ *.       luminance on a scale defined by the destination color gamut. Max(R, G, B) has been found
+ *.       to minimize difference in hue while restricting to typical LDR color volumes. See: Burke,
+ *.       Adam & Smith, Michael & Zink, Michael. 2020. Color Volume and Hue-preservation in HDR
+ *.       Tone Mapping. SMPTE Motion Imaging Journal.
+ *.    b. Each computed luminance is lower-bounded by 1.0 in Skia's color
+ *.       management, or 203 nits.
+ * 2. Blur the resulting chunks using a 5x5 gaussian kernel, to smooth out the local luminance map.
+ * 3. Now, for each pixel in the original image:
+ *     a. Upsample from the blurred chunks of luminance computed in (2). Call this luminance value
+ *.       L: an estimate of the maximum luminance of surrounding pixels.
+ *.    b. If the luminance is less than 1.0 (203 nits), then do not modify the pixel value of the
+ *.       original image.
+ *.    c. Otherwise,
+ *.       parameterize a tone-mapping curve using a method described by Chrome:
+ *.       https://docs.google.com/document/d/17T2ek1i2R7tXdfHCnM-i5n6__RoYe0JyMfKmTEjoGR8/.
+ *.        i. Compute a gain G = (1 + max(linear R, linear G, linear B) / (L * L))
+ *.           / (1 + max(linear R, linear G, linear B)). Note the similarity with the 1D curve
+ *.           described by Erik Reinhard, Michael Stark, Peter Shirley, and James Ferwerda. 2002.
+ *.           Photographic tone reproduction for digital images. ACM Trans. Graph.
+ *.       ii. Multiply G by the linear source colors to compute the final colors.
+ *
+ * Because it is a multi-renderpass algorithm requiring multiple off-screen textures, MouriMap is
+ * typically not suitable to be ran "frequently", at high refresh rates (e.g., 120hz). However,
+ * MouriMap is sufficiently fast enough for infrequent composition where preserving SDR detail is
+ * most important, such as for screenshots.
+ */
+class MouriMap {
+public:
+    MouriMap();
+    // Apply the MouriMap tonemmaping operator to the input.
+    // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger
+    // then 1.0 means that there is headroom above the SDR region.
+    // Similarly, the target HDR/SDR ratio describes the luminance range of the output.
+    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputHdrSdrRatio,
+                             float targetHdrSdrRatio);
+
+private:
+    sk_sp<SkImage> downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
+                             float hdrSdrRatio) const;
+    sk_sp<SkImage> blur(SkiaGpuContext* context, SkImage* input) const;
+    sk_sp<SkShader> tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+                            float targetHdrSdrRatio) const;
+    const sk_sp<SkRuntimeEffect> mCrosstalkAndChunk16x16;
+    const sk_sp<SkRuntimeEffect> mChunk8x8;
+    const sk_sp<SkRuntimeEffect> mBlur;
+    const sk_sp<SkRuntimeEffect> mTonemap;
+};
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index 0eea187..7fbbf49 100644
--- a/libs/renderengine/tests/Android.bp
+++ b/libs/renderengine/tests/Android.bp
@@ -46,6 +46,7 @@
         "libshaders",
         "libtonemap",
         "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
     ],
     header_libs: [
         "libtonemap_headers",
@@ -64,5 +65,7 @@
         "libui",
         "libutils",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
     ],
 }
diff --git a/libs/renderengine/tests/RenderEngineTest.cpp b/libs/renderengine/tests/RenderEngineTest.cpp
index 7b8eb84..b5cc65f 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -22,6 +22,7 @@
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
 #include <cutils/properties.h>
 #include <gtest/gtest.h>
 #include <renderengine/ExternalTexture.h>
@@ -33,14 +34,25 @@
 #include <ui/ColorSpace.h>
 #include <ui/PixelFormat.h>
 
+#include <algorithm>
 #include <chrono>
 #include <condition_variable>
+#include <filesystem>
 #include <fstream>
+#include <system_error>
 
 #include "../skia/SkiaGLRenderEngine.h"
 #include "../skia/SkiaVkRenderEngine.h"
 #include "../threaded/RenderEngineThreaded.h"
 
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE) || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(FORCE_COMPILE_GRAPHITE_RENDERENGINE)
+#define COMPILE_GRAPHITE_RENDERENGINE 1
+#else
+#define COMPILE_GRAPHITE_RENDERENGINE 0
+#endif
+
 constexpr int DEFAULT_DISPLAY_WIDTH = 128;
 constexpr int DEFAULT_DISPLAY_HEIGHT = 256;
 constexpr int DEFAULT_DISPLAY_OFFSET = 64;
@@ -107,6 +119,7 @@
 
     virtual std::string name() = 0;
     virtual renderengine::RenderEngine::GraphicsApi graphicsApi() = 0;
+    virtual renderengine::RenderEngine::SkiaBackend skiaBackend() = 0;
     bool apiSupported() { return renderengine::RenderEngine::canSupport(graphicsApi()); }
     std::unique_ptr<renderengine::RenderEngine> createRenderEngine() {
         renderengine::RenderEngineCreationArgs reCreationArgs =
@@ -115,24 +128,16 @@
                         .setImageCacheSize(1)
                         .setEnableProtectedContext(false)
                         .setPrecacheToneMapperShaderOnly(false)
-                        .setSupportsBackgroundBlur(true)
+                        .setBlurAlgorithm(renderengine::RenderEngine::BlurAlgorithm::KAWASE)
                         .setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
                         .setThreaded(renderengine::RenderEngine::Threaded::NO)
                         .setGraphicsApi(graphicsApi())
+                        .setSkiaBackend(skiaBackend())
                         .build();
         return renderengine::RenderEngine::create(reCreationArgs);
     }
 };
 
-class SkiaVkRenderEngineFactory : public RenderEngineFactory {
-public:
-    std::string name() override { return "SkiaVkRenderEngineFactory"; }
-
-    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
-        return renderengine::RenderEngine::GraphicsApi::VK;
-    }
-};
-
 class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
 public:
     std::string name() override { return "SkiaGLRenderEngineFactory"; }
@@ -140,8 +145,41 @@
     renderengine::RenderEngine::GraphicsApi graphicsApi() {
         return renderengine::RenderEngine::GraphicsApi::GL;
     }
+
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GANESH;
+    }
 };
 
+class GaneshVkRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "GaneshVkRenderEngineFactory"; }
+
+    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+        return renderengine::RenderEngine::GraphicsApi::VK;
+    }
+
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GANESH;
+    }
+};
+
+// TODO: b/341728634 - Clean up conditional compilation.
+#if COMPILE_GRAPHITE_RENDERENGINE
+class GraphiteVkRenderEngineFactory : public RenderEngineFactory {
+public:
+    std::string name() override { return "GraphiteVkRenderEngineFactory"; }
+
+    renderengine::RenderEngine::GraphicsApi graphicsApi() override {
+        return renderengine::RenderEngine::GraphicsApi::VK;
+    }
+
+    renderengine::RenderEngine::SkiaBackend skiaBackend() override {
+        return renderengine::RenderEngine::SkiaBackend::GRAPHITE;
+    }
+};
+#endif
+
 class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
 public:
     std::shared_ptr<renderengine::ExternalTexture> allocateDefaultBuffer() {
@@ -219,27 +257,56 @@
     RenderEngineTest() {
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
     ~RenderEngineTest() {
         if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
-            writeBufferToFile("/data/texture_out_");
+            writeBufferToFile("/data/local/tmp/RenderEngineTest/");
         }
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+        ALOGI("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
     }
 
-    void writeBufferToFile(const char* basename) {
-        std::string filename(basename);
-        filename.append(::testing::UnitTest::GetInstance()->current_test_info()->name());
-        filename.append(".ppm");
-        std::ofstream file(filename.c_str(), std::ios::binary);
+    // If called during e.g.
+    // `PerRenderEngineType/RenderEngineTest#drawLayers_fillBufferCheckersRotate90_colorSource/0`
+    // with a directory of `/data/local/tmp/RenderEngineTest`, then mBuffer will be dumped to
+    // `/data/local/tmp/RenderEngineTest/drawLayers_fillBufferCheckersRotate90_colorSource-0.ppm`
+    //
+    // Note: if `directory` does not exist, then its full path will be recursively created with 777
+    // permissions. If `directory` already exists but does not grant the executing user write
+    // permissions, then saving the buffer will fail.
+    //
+    // Since this is test-only code, no security considerations are made.
+    void writeBufferToFile(const filesystem::path& directory) {
+        const auto currentTestInfo = ::testing::UnitTest::GetInstance()->current_test_info();
+        LOG_ALWAYS_FATAL_IF(!currentTestInfo,
+                            "writeBufferToFile must be called during execution of a test");
+
+        std::string fileName(currentTestInfo->name());
+        // Test names may include the RenderEngine variant separated by '/', which would separate
+        // the file name into a subdirectory if not corrected.
+        std::replace(fileName.begin(), fileName.end(), '/', '-');
+        fileName.append(".ppm");
+
+        std::error_code err;
+        filesystem::create_directories(directory, err);
+        if (err.value()) {
+            ALOGE("Unable to create directory %s for writing %s (%d: %s)", directory.c_str(),
+                  fileName.c_str(), err.value(), err.message().c_str());
+            return;
+        }
+
+        // Append operator ("/") ensures exactly one "/" directly before the argument.
+        const filesystem::path filePath = directory / fileName;
+        std::ofstream file(filePath.c_str(), std::ios::binary);
         if (!file.is_open()) {
-            ALOGE("Unable to open file: %s", filename.c_str());
-            ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
-                  "surfaceflinger to write debug images");
+            ALOGE("Unable to open file: %s", filePath.c_str());
+            ALOGE("You may need to do: \"adb shell setenforce 0\" to enable surfaceflinger to "
+                  "write debug images, or the %s directory might not give the executing user write "
+                  "permission",
+                  directory.c_str());
             return;
         }
 
@@ -269,6 +336,7 @@
             }
         }
         file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
+        ALOGI("Image of incorrect output written to %s", filePath.c_str());
         mBuffer->getBuffer()->unlock();
     }
 
@@ -1242,7 +1310,12 @@
 
 void RenderEngineTest::fillBufferWithPremultiplyAlpha() {
     fillRedBufferWithPremultiplyAlpha();
-    expectBufferColor(fullscreenRect(), 128, 0, 0, 128);
+    // Different backends and GPUs may round 255 * 0.5 = 127.5 differently, but
+    // either 127 or 128 are acceptable. Checking both 127 and 128 with a
+    // tolerance of 1 allows either 127 or 128 to pass, while preventing 126 or
+    // 129 from erroneously passing.
+    expectBufferColor(fullscreenRect(), 127, 0, 0, 127, 1);
+    expectBufferColor(fullscreenRect(), 128, 0, 0, 128, 1);
 }
 
 void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
@@ -1469,9 +1542,15 @@
     expectBufferColor(Rect(kGreyLevels, 1), generator, 2);
 }
 
+// TODO: b/341728634 - Clean up conditional compilation.
 INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
                          testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
-                                         std::make_shared<SkiaVkRenderEngineFactory>()));
+                                         std::make_shared<GaneshVkRenderEngineFactory>()
+#if COMPILE_GRAPHITE_RENDERENGINE
+                                                 ,
+                                         std::make_shared<GraphiteVkRenderEngineFactory>()
+#endif
+                                                 ));
 
 TEST_P(RenderEngineTest, drawLayers_noLayersToDraw) {
     if (!GetParam()->apiSupported()) {
@@ -2490,51 +2569,6 @@
     expectBufferColor(rect, 0, 128, 0, 128);
 }
 
-TEST_P(RenderEngineTest, testBorder) {
-    if (!GetParam()->apiSupported()) {
-        GTEST_SKIP();
-    }
-
-    initializeRenderEngine();
-
-    const ui::Dataspace dataspace = ui::Dataspace::V0_SRGB;
-
-    const auto displayRect = Rect(1080, 2280);
-    renderengine::DisplaySettings display{
-            .physicalDisplay = displayRect,
-            .clip = displayRect,
-            .outputDataspace = dataspace,
-    };
-    display.borderInfoList.clear();
-    renderengine::BorderRenderInfo info;
-    info.combinedRegion = Region(Rect(99, 99, 199, 199));
-    info.width = 20.0f;
-    info.color = half4{1.0f, 128.0f / 255.0f, 0.0f, 1.0f};
-    display.borderInfoList.emplace_back(info);
-
-    const auto greenBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(0, 255, 0, 255));
-    const renderengine::LayerSettings greenLayer{
-            .geometry.boundaries = FloatRect(0.f, 0.f, 1.f, 1.f),
-            .source =
-                    renderengine::PixelSource{
-                            .buffer =
-                                    renderengine::Buffer{
-                                            .buffer = greenBuffer,
-                                            .usePremultipliedAlpha = true,
-                                    },
-                    },
-            .alpha = 1.0f,
-            .sourceDataspace = dataspace,
-            .whitePointNits = 200.f,
-    };
-
-    std::vector<renderengine::LayerSettings> layers;
-    layers.emplace_back(greenLayer);
-    invokeDraw(display, layers);
-
-    expectBufferColor(Rect(99, 99, 101, 101), 255, 128, 0, 255, 1);
-}
-
 TEST_P(RenderEngineTest, testDimming) {
     if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
@@ -3146,13 +3180,228 @@
     expectBufferColor(Rect(0, 0, 1, 1), 0,  70, 0, 255);
 }
 
+TEST_P(RenderEngineTest, localTonemap_preservesFullscreenSdr) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    ASSERT_EQ(0, mBuffer->getBuffer()->initCheck());
+
+    const auto whiteBuffer = allocateAndFillSourceBuffer(1, 1, ubyte4(51, 51, 51, 255));
+
+    const auto rect = Rect(0, 0, 1, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::SRGB,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = whiteBuffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    expectBufferColor(Rect(0, 0, 1, 1), 255, 255, 255, 255);
+}
+
+TEST_P(RenderEngineTest, localTonemap_preservesFarawaySdrRegions) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    const auto blockWidth = 256;
+    const auto width = blockWidth * 4;
+
+    const auto buffer = allocateSourceBuffer(width, 1);
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(width, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                             1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    {
+        uint8_t* pixels;
+        buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                  reinterpret_cast<void**>(&pixels));
+        uint8_t* dst = pixels;
+        for (uint32_t i = 0; i < width; i++) {
+            uint8_t value = 0;
+            if (i < blockWidth) {
+                value = 51;
+            } else if (i >= blockWidth * 3) {
+                value = 255;
+            }
+            dst[0] = value;
+            dst[1] = value;
+            dst[2] = value;
+            dst[3] = 255;
+            dst += 4;
+        }
+        buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, width, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    // SDR regions are boosted to preserve SDR detail.
+    expectBufferColor(Rect(0, 0, blockWidth, 1), 255, 255, 255, 255);
+    expectBufferColor(Rect(blockWidth, 0, blockWidth * 2, 1), 0, 0, 0, 255);
+    expectBufferColor(Rect(blockWidth * 2, 0, blockWidth * 3, 1), 0, 0, 0, 255);
+    expectBufferColor(Rect(blockWidth * 3, 0, blockWidth * 4, 1), 255, 255, 255, 255);
+}
+
+TEST_P(RenderEngineTest, localTonemap_tonemapsNearbySdrRegions) {
+    if (!GetParam()->apiSupported()) {
+        GTEST_SKIP();
+    }
+
+    initializeRenderEngine();
+
+    const auto blockWidth = 2;
+    const auto width = blockWidth * 2;
+
+    mBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(width, 1, HAL_PIXEL_FORMAT_RGBA_8888,
+                                                             1,
+                                                             GRALLOC_USAGE_SW_READ_OFTEN |
+                                                                     GRALLOC_USAGE_SW_WRITE_OFTEN |
+                                                                     GRALLOC_USAGE_HW_RENDER |
+                                                                     GRALLOC_USAGE_HW_TEXTURE,
+                                                             "output"),
+                                     *mRE,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+
+    const auto buffer = allocateSourceBuffer(width, 1);
+
+    {
+        uint8_t* pixels;
+        buffer->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                  reinterpret_cast<void**>(&pixels));
+        uint8_t* dst = pixels;
+        for (uint32_t i = 0; i < width; i++) {
+            uint8_t value = 0;
+            if (i < blockWidth) {
+                value = 51;
+            } else if (i >= blockWidth) {
+                value = 255;
+            }
+            dst[0] = value;
+            dst[1] = value;
+            dst[2] = value;
+            dst[3] = 255;
+            dst += 4;
+        }
+        buffer->getBuffer()->unlock();
+    }
+
+    const auto rect = Rect(0, 0, width, 1);
+    const renderengine::DisplaySettings display{
+            .physicalDisplay = rect,
+            .clip = rect,
+            .outputDataspace = ui::Dataspace::V0_SRGB_LINEAR,
+            .targetLuminanceNits = 40,
+            .tonemapStrategy = renderengine::DisplaySettings::TonemapStrategy::Local,
+    };
+
+    const renderengine::LayerSettings whiteLayer{
+            .geometry.boundaries = rect.toFloatRect(),
+            .source =
+                    renderengine::PixelSource{
+                            .buffer =
+                                    renderengine::Buffer{
+                                            .buffer = buffer,
+                                    },
+                    },
+            .alpha = 1.0f,
+            .sourceDataspace = ui::Dataspace::V0_SCRGB_LINEAR,
+            .whitePointNits = 200,
+    };
+
+    std::vector<renderengine::LayerSettings> layers{whiteLayer};
+    invokeDraw(display, layers);
+
+    // SDR regions remain "dimmed", but preserve detail with a roll-off curve.
+    expectBufferColor(Rect(0, 0, blockWidth, 1), 132, 132, 132, 255, 2);
+    // HDR regions are not dimmed.
+    expectBufferColor(Rect(blockWidth, 0, blockWidth * 2, 1), 255, 255, 255, 255);
+}
+
 TEST_P(RenderEngineTest, primeShaderCache) {
+    // TODO: b/331447071 - Fix in Graphite and re-enable.
+    if (GetParam()->skiaBackend() == renderengine::RenderEngine::SkiaBackend::GRAPHITE) {
+        GTEST_SKIP();
+    }
+
     if (!GetParam()->apiSupported()) {
         GTEST_SKIP();
     }
     initializeRenderEngine();
 
-    auto fut = mRE->primeCache(false);
+    PrimeCacheConfig config;
+    config.cacheUltraHDR = false;
+    auto fut = mRE->primeCache(config);
     if (fut.valid()) {
         fut.wait();
     }
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index d56dbb2..bdd9402 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -25,6 +25,7 @@
 
 namespace android {
 
+using renderengine::PrimeCacheConfig;
 using testing::_;
 using testing::Eq;
 using testing::Mock;
@@ -48,9 +49,25 @@
     mThreadedRE->dump(testString);
 }
 
+MATCHER_P(EqConfig, other, "Equality for prime cache config") {
+    return arg.cacheHolePunchLayer == other.cacheHolePunchLayer &&
+            arg.cacheSolidLayers == other.cacheSolidLayers &&
+            arg.cacheSolidDimmedLayers == other.cacheSolidDimmedLayers &&
+            arg.cacheImageLayers == other.cacheImageLayers &&
+            arg.cacheImageDimmedLayers == other.cacheImageDimmedLayers &&
+            arg.cacheClippedLayers == other.cacheClippedLayers &&
+            arg.cacheShadowLayers == other.cacheShadowLayers &&
+            arg.cachePIPImageLayers == other.cachePIPImageLayers &&
+            arg.cacheTransparentImageDimmedLayers == other.cacheTransparentImageDimmedLayers &&
+            arg.cacheClippedDimmedImageLayers == other.cacheClippedDimmedImageLayers &&
+            arg.cacheUltraHDR == other.cacheUltraHDR;
+}
+
 TEST_F(RenderEngineThreadedTest, primeCache) {
-    EXPECT_CALL(*mRenderEngine, primeCache(false));
-    mThreadedRE->primeCache(false);
+    PrimeCacheConfig config;
+    config.cacheUltraHDR = false;
+    EXPECT_CALL(*mRenderEngine, primeCache(EqConfig(config)));
+    mThreadedRE->primeCache(config);
     // need to call ANY synchronous function after primeCache to ensure that primeCache has
     // completed asynchronously before the test completes execution.
     mThreadedRE->getContextPriority();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index f4cebc0..c187f93 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -23,9 +23,9 @@
 #include <future>
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <private/gui/SyncFeatures.h>
 #include <processgroup/processgroup.h>
-#include <utils/Trace.h>
 
 using namespace std::chrono_literals;
 
@@ -39,7 +39,7 @@
 
 RenderEngineThreaded::RenderEngineThreaded(CreateInstanceFactory factory)
       : RenderEngine(Threaded::YES) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard lockThread(mThreadMutex);
     mThread = std::thread(&RenderEngineThreaded::threadMain, this, factory);
@@ -76,7 +76,7 @@
 
 // NO_THREAD_SAFETY_ANALYSIS is because std::unique_lock presently lacks thread safety annotations.
 void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_SAFETY_ANALYSIS {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!SetTaskProfiles(0, {"SFRenderEnginePolicy"})) {
         ALOGW("Failed to set render-engine task profile!");
@@ -130,28 +130,27 @@
     }
 }
 
-std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
+std::future<void> RenderEngineThreaded::primeCache(PrimeCacheConfig config) {
     const auto resultPromise = std::make_shared<std::promise<void>>();
     std::future<void> resultFuture = resultPromise->get_future();
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // This function is designed so it can run asynchronously, so we do not need to wait
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
-        mFunctionCalls.push(
-                [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
-                    ATRACE_NAME("REThreaded::primeCache");
-                    if (setSchedFifo(false) != NO_ERROR) {
-                        ALOGW("Couldn't set SCHED_OTHER for primeCache");
-                    }
+        mFunctionCalls.push([resultPromise, config](renderengine::RenderEngine& instance) {
+            SFTRACE_NAME("REThreaded::primeCache");
+            if (setSchedFifo(false) != NO_ERROR) {
+                ALOGW("Couldn't set SCHED_OTHER for primeCache");
+            }
 
-                    instance.primeCache(shouldPrimeUltraHDR);
-                    resultPromise->set_value();
+            instance.primeCache(config);
+            resultPromise->set_value();
 
-                    if (setSchedFifo(true) != NO_ERROR) {
-                        ALOGW("Couldn't set SCHED_FIFO for primeCache");
-                    }
-                });
+            if (setSchedFifo(true) != NO_ERROR) {
+                ALOGW("Couldn't set SCHED_FIFO for primeCache");
+            }
+        });
     }
     mCondition.notify_one();
 
@@ -164,7 +163,7 @@
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([&resultPromise, &result](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::dump");
+            SFTRACE_NAME("REThreaded::dump");
             std::string localResult = result;
             instance.dump(localResult);
             resultPromise.set_value(std::move(localResult));
@@ -177,13 +176,13 @@
 
 void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
                                                     bool isRenderable) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // This function is designed so it can run asynchronously, so we do not need to wait
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([=](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::mapExternalTextureBuffer");
+            SFTRACE_NAME("REThreaded::mapExternalTextureBuffer");
             instance.mapExternalTextureBuffer(buffer, isRenderable);
         });
     }
@@ -191,14 +190,14 @@
 }
 
 void RenderEngineThreaded::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // This function is designed so it can run asynchronously, so we do not need to wait
     // for the futures.
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push(
                 [=, buffer = std::move(buffer)](renderengine::RenderEngine& instance) mutable {
-                    ATRACE_NAME("REThreaded::unmapExternalTextureBuffer");
+                    SFTRACE_NAME("REThreaded::unmapExternalTextureBuffer");
                     instance.unmapExternalTextureBuffer(std::move(buffer));
                 });
     }
@@ -230,7 +229,7 @@
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([=](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::cleanupPostRender");
+            SFTRACE_NAME("REThreaded::cleanupPostRender");
             instance.cleanupPostRender();
         });
         mNeedsPostRenderCleanup = false;
@@ -250,10 +249,20 @@
     return;
 }
 
+void RenderEngineThreaded::drawGainmapInternal(
+        const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    resultPromise->set_value(Fence::NO_FENCE);
+    return;
+}
+
 ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
         const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
     int fd = bufferFence.release();
@@ -262,8 +271,8 @@
         mNeedsPostRenderCleanup = true;
         mFunctionCalls.push(
                 [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
-                    ATRACE_NAME("REThreaded::drawLayers");
-                    instance.updateProtectedContext(layers, buffer);
+                    SFTRACE_NAME("REThreaded::drawLayers");
+                    instance.updateProtectedContext(layers, {buffer.get()});
                     instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
                                                 base::unique_fd(fd));
                 });
@@ -272,13 +281,37 @@
     return resultFuture;
 }
 
+ftl::Future<FenceResult> RenderEngineThreaded::drawGainmap(
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    SFTRACE_CALL();
+    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+    std::future<FenceResult> resultFuture = resultPromise->get_future();
+    {
+        std::lock_guard lock(mThreadMutex);
+        mNeedsPostRenderCleanup = true;
+        mFunctionCalls.push([resultPromise, sdr, sdrFence = std::move(sdrFence), hdr,
+                             hdrFence = std::move(hdrFence), hdrSdrRatio, dataspace,
+                             gainmap](renderengine::RenderEngine& instance) mutable {
+            SFTRACE_NAME("REThreaded::drawGainmap");
+            instance.updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
+            instance.drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
+                                         std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+        });
+    }
+    mCondition.notify_one();
+    return resultFuture;
+}
+
 int RenderEngineThreaded::getContextPriority() {
     std::promise<int> resultPromise;
     std::future<int> resultFuture = resultPromise.get_future();
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([&resultPromise](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::getContextPriority");
+            SFTRACE_NAME("REThreaded::getContextPriority");
             int priority = instance.getContextPriority();
             resultPromise.set_value(priority);
         });
@@ -298,7 +331,7 @@
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([size](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::onActiveDisplaySizeChanged");
+            SFTRACE_NAME("REThreaded::onActiveDisplaySizeChanged");
             instance.onActiveDisplaySizeChanged(size);
         });
     }
@@ -325,7 +358,7 @@
     {
         std::lock_guard lock(mThreadMutex);
         mFunctionCalls.push([tracingEnabled](renderengine::RenderEngine& instance) {
-            ATRACE_NAME("REThreaded::setEnableTracing");
+            SFTRACE_NAME("REThreaded::setEnableTracing");
             instance.setEnableTracing(tracingEnabled);
         });
     }
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index d440c96..cb6e924 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -41,7 +41,7 @@
 
     RenderEngineThreaded(CreateInstanceFactory factory);
     ~RenderEngineThreaded() override;
-    std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
+    std::future<void> primeCache(PrimeCacheConfig config) override;
 
     void dump(std::string& result) override;
 
@@ -55,6 +55,12 @@
                                         const std::vector<LayerSettings>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
                                         base::unique_fd&& bufferFence) override;
+    ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
+                                         base::borrowed_fd&& sdrFence,
+                                         const std::shared_ptr<ExternalTexture>& hdr,
+                                         base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                                         ui::Dataspace dataspace,
+                                         const std::shared_ptr<ExternalTexture>& gainmap) override;
 
     int getContextPriority() override;
     bool supportsBackgroundBlur() override;
@@ -71,6 +77,13 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override;
+    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                             const std::shared_ptr<ExternalTexture>& sdr,
+                             base::borrowed_fd&& sdrFence,
+                             const std::shared_ptr<ExternalTexture>& hdr,
+                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                             ui::Dataspace dataspace,
+                             const std::shared_ptr<ExternalTexture>& gainmap) override;
 
 private:
     void threadMain(CreateInstanceFactory factory);
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index 7fa47b4..659666d 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -63,6 +63,8 @@
         "libhardware",
         "libpermission",
         "android.companion.virtual.virtualdevice_aidl-cpp",
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
 
     static_libs: [
diff --git a/libs/sensor/OWNERS b/libs/sensor/OWNERS
index 7347ac7..4929b3f 100644
--- a/libs/sensor/OWNERS
+++ b/libs/sensor/OWNERS
@@ -1 +1 @@
-bduddie@google.com
+include platform/frameworks/native:/services/sensorservice/OWNERS
\ No newline at end of file
diff --git a/libs/sensor/SensorEventQueue.cpp b/libs/sensor/SensorEventQueue.cpp
index 4438d45..bec9255 100644
--- a/libs/sensor/SensorEventQueue.cpp
+++ b/libs/sensor/SensorEventQueue.cpp
@@ -15,31 +15,41 @@
  */
 
 #define LOG_TAG "Sensors"
-
-#include <sensor/SensorEventQueue.h>
-
-#include <algorithm>
-#include <sys/socket.h>
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <sensor/Sensor.h>
-#include <sensor/BitTube.h>
-#include <sensor/ISensorEventConnection.h>
+#define ATRACE_TAG ATRACE_TAG_SYSTEM_SERVER
 
 #include <android/sensor.h>
+#include <com_android_hardware_libsensor_flags.h>
+#include <cutils/trace.h>
 #include <hardware/sensors-base.h>
+#include <sensor/BitTube.h>
+#include <sensor/ISensorEventConnection.h>
+#include <sensor/Sensor.h>
+#include <sensor/SensorEventQueue.h>
+#include <sensor/SensorManager.h>
+#include <sys/socket.h>
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
+
+#include <algorithm>
+#include <cinttypes>
+#include <string>
 
 using std::min;
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
 // ----------------------------------------------------------------------------
 
-SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection)
-    : mSensorEventConnection(connection), mRecBuffer(nullptr), mAvailable(0), mConsumed(0),
-      mNumAcksToSend(0) {
+SensorEventQueue::SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                                   SensorManager& sensorManager, String8 packageName)
+      : mSensorEventConnection(connection),
+        mRecBuffer(nullptr),
+        mSensorManager(sensorManager),
+        mPackageName(packageName),
+        mAvailable(0),
+        mConsumed(0),
+        mNumAcksToSend(0) {
     mRecBuffer = new ASensorEvent[MAX_RECEIVE_BUFFER_EVENT_COUNT];
 }
 
@@ -65,8 +75,8 @@
 
 ssize_t SensorEventQueue::read(ASensorEvent* events, size_t numEvents) {
     if (mAvailable == 0) {
-        ssize_t err = BitTube::recvObjects(mSensorChannel,
-                mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
+        ssize_t err =
+                BitTube::recvObjects(mSensorChannel, mRecBuffer, MAX_RECEIVE_BUFFER_EVENT_COUNT);
         if (err < 0) {
             return err;
         }
@@ -75,6 +85,23 @@
     }
     size_t count = min(numEvents, mAvailable);
     memcpy(events, mRecBuffer + mConsumed, count * sizeof(ASensorEvent));
+
+    if (CC_UNLIKELY(ATRACE_ENABLED()) &&
+        libsensor_flags::sensor_event_queue_report_sensor_usage_in_tracing()) {
+        for (size_t i = 0; i < count; i++) {
+            std::optional<std::string_view> sensorName =
+                    mSensorManager.getSensorNameByHandle(events->sensor);
+            if (sensorName.has_value()) {
+                char buffer[UINT8_MAX];
+                IPCThreadState* thread = IPCThreadState::self();
+                pid_t pid = (thread != nullptr) ? thread->getCallingPid() : -1;
+                std::snprintf(buffer, sizeof(buffer),
+                              "Sensor event from %s to %s PID: %d (%zu/%zu)",
+                              sensorName.value().data(), mPackageName.c_str(), pid, i, count);
+                ATRACE_INSTANT_FOR_TRACK(LOG_TAG, buffer);
+            }
+        }
+    }
     mAvailable -= count;
     mConsumed += count;
     return static_cast<ssize_t>(count);
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 9411e20..7b4a86c 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -38,6 +38,7 @@
 #include <sensor/SensorEventQueue.h>
 
 #include <com_android_hardware_libsensor_flags.h>
+namespace libsensor_flags = com::android::hardware::libsensor::flags;
 
 // ----------------------------------------------------------------------------
 namespace android {
@@ -72,12 +73,25 @@
                 return deviceId;
             }
         }
-    } else {
-        ALOGW("Cannot get virtualdevice_native service");
     }
     return DEVICE_ID_DEFAULT;
 }
 
+bool findSensorNameInList(int32_t handle, const Vector<Sensor>& sensorList,
+                          std::string* outString) {
+    for (auto& sensor : sensorList) {
+        if (sensor.getHandle() == handle) {
+            std::ostringstream oss;
+            oss << sensor.getStringType() << ":" << sensor.getName();
+            if (outString) {
+                *outString = oss.str();
+            }
+            return true;
+        }
+    }
+    return false;
+}
+
 }  // namespace
 
 Mutex SensorManager::sLock;
@@ -355,6 +369,25 @@
     return nullptr;
 }
 
+std::optional<std::string_view> SensorManager::getSensorNameByHandle(int32_t handle) {
+    std::lock_guard<std::mutex> lock(mSensorHandleToNameMutex);
+    auto iterator = mSensorHandleToName.find(handle);
+    if (iterator != mSensorHandleToName.end()) {
+        return iterator->second;
+    }
+
+    std::string sensorName;
+    if (!findSensorNameInList(handle, mSensors, &sensorName) &&
+        !findSensorNameInList(handle, mDynamicSensors, &sensorName)) {
+        ALOGW("Cannot find sensor with handle %d", handle);
+        return std::nullopt;
+    }
+
+    mSensorHandleToName[handle] = std::move(sensorName);
+
+    return mSensorHandleToName[handle];
+}
+
 sp<SensorEventQueue> SensorManager::createEventQueue(
     String8 packageName, int mode, String16 attributionTag) {
     sp<SensorEventQueue> queue;
@@ -368,7 +401,7 @@
             ALOGE("createEventQueue: connection is NULL.");
             return nullptr;
         }
-        queue = new SensorEventQueue(connection);
+        queue = new SensorEventQueue(connection, *this, packageName);
         break;
     }
     return queue;
diff --git a/libs/sensor/include/sensor/SensorEventQueue.h b/libs/sensor/include/sensor/SensorEventQueue.h
index 8c3fde0..d31def7 100644
--- a/libs/sensor/include/sensor/SensorEventQueue.h
+++ b/libs/sensor/include/sensor/SensorEventQueue.h
@@ -20,9 +20,10 @@
 #include <sys/types.h>
 
 #include <utils/Errors.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
 #include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Timers.h>
 
 #include <sensor/BitTube.h>
 
@@ -42,6 +43,7 @@
 // ----------------------------------------------------------------------------
 
 class ISensorEventConnection;
+class SensorManager;
 class Sensor;
 class Looper;
 
@@ -65,7 +67,8 @@
     // Default sensor sample period
     static constexpr int32_t SENSOR_DELAY_NORMAL = 200000;
 
-    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection);
+    explicit SensorEventQueue(const sp<ISensorEventConnection>& connection,
+                              SensorManager& sensorManager, String8 packageName);
     virtual ~SensorEventQueue();
     virtual void onFirstRef();
 
@@ -107,6 +110,8 @@
     mutable Mutex mLock;
     mutable sp<Looper> mLooper;
     ASensorEvent* mRecBuffer;
+    SensorManager& mSensorManager;
+    String8 mPackageName;
     size_t mAvailable;
     size_t mConsumed;
     uint32_t mNumAcksToSend;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 49f050a..8d7237d 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -17,22 +17,20 @@
 #ifndef ANDROID_GUI_SENSOR_MANAGER_H
 #define ANDROID_GUI_SENSOR_MANAGER_H
 
-#include <map>
-#include <unordered_map>
-
-#include <stdint.h>
-#include <sys/types.h>
-
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
-
+#include <sensor/SensorEventQueue.h>
+#include <stdint.h>
+#include <sys/types.h>
 #include <utils/Errors.h>
+#include <utils/String8.h>
 #include <utils/StrongPointer.h>
 #include <utils/Vector.h>
-#include <utils/String8.h>
 
-#include <sensor/SensorEventQueue.h>
+#include <map>
+#include <string>
+#include <unordered_map>
 
 // ----------------------------------------------------------------------------
 // Concrete types for the NDK
@@ -66,6 +64,7 @@
     sp<SensorEventQueue> createEventQueue(
         String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
     bool isDataInjectionEnabled();
+    std::optional<std::string_view> getSensorNameByHandle(int32_t handle);
     bool isReplayDataInjectionEnabled();
     bool isHalBypassReplayDataInjectionEnabled();
     int createDirectChannel(size_t size, int channelType, const native_handle_t *channelData);
@@ -97,6 +96,9 @@
     const String16 mOpPackageName;
     const int mDeviceId;
     std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
+
+    std::mutex mSensorHandleToNameMutex;
+    std::unordered_map<int32_t, std::string> mSensorHandleToName;
     int32_t mDirectConnectionHandle;
 };
 
diff --git a/libs/sensor/libsensor_flags.aconfig b/libs/sensor/libsensor_flags.aconfig
index c511f4a..cbf3055 100644
--- a/libs/sensor/libsensor_flags.aconfig
+++ b/libs/sensor/libsensor_flags.aconfig
@@ -8,3 +8,10 @@
   bug: "322228259"
   is_fixed_read_only: true
 }
+
+flag {
+  name: "sensor_event_queue_report_sensor_usage_in_tracing"
+  namespace: "sensors"
+  description: "When this flag is enabled, sensor event queue will report sensor usage when system trace is enabled."
+  bug: "333132224"
+}
\ No newline at end of file
diff --git a/libs/sensorprivacy/Android.bp b/libs/sensorprivacy/Android.bp
index 1e7e707..00514c4 100644
--- a/libs/sensorprivacy/Android.bp
+++ b/libs/sensorprivacy/Android.bp
@@ -57,7 +57,6 @@
 filegroup {
     name: "libsensorprivacy_aidl",
     srcs: [
-        "aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl",
         "aidl/android/hardware/ISensorPrivacyListener.aidl",
         "aidl/android/hardware/ISensorPrivacyManager.aidl",
     ],
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index fe93786..3f3ad93 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -155,10 +155,9 @@
     return DISABLED;
 }
 
-std::vector<hardware::CameraPrivacyAllowlistEntry>
-        SensorPrivacyManager::getCameraPrivacyAllowlist(){
+std::vector<String16> SensorPrivacyManager::getCameraPrivacyAllowlist(){
     sp<hardware::ISensorPrivacyManager> service = getService();
-    std::vector<hardware::CameraPrivacyAllowlistEntry> result;
+    std::vector<String16> result;
     if (service != nullptr) {
         service->getCameraPrivacyAllowlist(&result);
         return result;
diff --git a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl b/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl
deleted file mode 100644
index 03e1537..0000000
--- a/libs/sensorprivacy/aidl/android/hardware/CameraPrivacyAllowlistEntry.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2024, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware;
-
-parcelable CameraPrivacyAllowlistEntry {
-    String packageName;
-    boolean isMandatory;
-}
diff --git a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
index b6bd39e..f707187 100644
--- a/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
+++ b/libs/sensorprivacy/aidl/android/hardware/ISensorPrivacyManager.aidl
@@ -16,7 +16,6 @@
 
 package android.hardware;
 
-import android.hardware.CameraPrivacyAllowlistEntry;
 import android.hardware.ISensorPrivacyListener;
 
 /** @hide */
@@ -43,7 +42,7 @@
 
     void setToggleSensorPrivacyForProfileGroup(int userId, int source, int sensor, boolean enable);
 
-    List<CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+    List<String> getCameraPrivacyAllowlist();
 
     int getToggleSensorPrivacyState(int toggleType, int sensor);
 
diff --git a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
index 9e97e16..8935b76 100644
--- a/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
+++ b/libs/sensorprivacy/include/sensorprivacy/SensorPrivacyManager.h
@@ -45,9 +45,7 @@
     enum {
         ENABLED = 1,
         DISABLED = 2,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_HELPFUL_APPS = 3,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_REQUIRED_APPS = 4,
-        AUTOMOTIVE_DRIVER_ASSISTANCE_APPS = 5
+        ENABLED_EXCEPT_ALLOWLISTED_APPS = 3
     };
 
     SensorPrivacyManager();
@@ -62,7 +60,7 @@
     bool isToggleSensorPrivacyEnabled(int toggleType, int sensor);
     status_t isToggleSensorPrivacyEnabled(int toggleType, int sensor, bool &result);
     int getToggleSensorPrivacyState(int toggleType, int sensor);
-    std::vector<hardware::CameraPrivacyAllowlistEntry> getCameraPrivacyAllowlist();
+    std::vector<String16> getCameraPrivacyAllowlist();
     bool isCameraPrivacyEnabled(String16 packageName);
 
     status_t linkToDeath(const sp<IBinder::DeathRecipient>& recipient);
diff --git a/libs/shaders/OWNERS b/libs/shaders/OWNERS
index 6d91da3..6977a49 100644
--- a/libs/shaders/OWNERS
+++ b/libs/shaders/OWNERS
@@ -1,4 +1,3 @@
 alecmouri@google.com
 jreck@google.com
 sallyqi@google.com
-scroggo@google.com
\ No newline at end of file
diff --git a/libs/tonemap/OWNERS b/libs/tonemap/OWNERS
index 6d91da3..6977a49 100644
--- a/libs/tonemap/OWNERS
+++ b/libs/tonemap/OWNERS
@@ -1,4 +1,3 @@
 alecmouri@google.com
 jreck@google.com
 sallyqi@google.com
-scroggo@google.com
\ No newline at end of file
diff --git a/libs/tracing_perfetto/.clang-format b/libs/tracing_perfetto/.clang-format
new file mode 100644
index 0000000..f397454
--- /dev/null
+++ b/libs/tracing_perfetto/.clang-format
@@ -0,0 +1,12 @@
+BasedOnStyle: Google
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
+
+ColumnLimit: 80
+ContinuationIndentWidth: 4
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+IndentWidth: 2
+PointerAlignment: Left
+UseTab: Never
+PenaltyExcessCharacter: 32
\ No newline at end of file
diff --git a/libs/tracing_perfetto/Android.bp b/libs/tracing_perfetto/Android.bp
new file mode 100644
index 0000000..b5c56c5
--- /dev/null
+++ b/libs/tracing_perfetto/Android.bp
@@ -0,0 +1,50 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_shared {
+    name: "libtracing_perfetto",
+    export_include_dirs: [
+        "include",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-enum-compare",
+        "-Wno-unused-function",
+    ],
+
+    srcs: [
+        "tracing_perfetto.cpp",
+        "tracing_perfetto_internal.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libcutils",
+        "libperfetto_c",
+        "android.os.flags-aconfig-cc-host",
+    ],
+
+    host_supported: true,
+}
diff --git a/libs/tracing_perfetto/OWNERS b/libs/tracing_perfetto/OWNERS
new file mode 100644
index 0000000..e2d4b46
--- /dev/null
+++ b/libs/tracing_perfetto/OWNERS
@@ -0,0 +1,2 @@
+zezeozue@google.com
+biswarupp@google.com
\ No newline at end of file
diff --git a/libs/tracing_perfetto/TEST_MAPPING b/libs/tracing_perfetto/TEST_MAPPING
new file mode 100644
index 0000000..1805e18
--- /dev/null
+++ b/libs/tracing_perfetto/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libtracing_perfetto_tests"
+    }
+  ]
+}
diff --git a/libs/tracing_perfetto/include/trace_categories.h b/libs/tracing_perfetto/include/trace_categories.h
new file mode 100644
index 0000000..6d4168b
--- /dev/null
+++ b/libs/tracing_perfetto/include/trace_categories.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2024 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 TRACE_CATEGORIES_H
+#define TRACE_CATEGORIES_H
+
+/**
+ * Keep these in sync with frameworks/base/core/java/android/os/Trace.java.
+ */
+#define TRACE_CATEGORY_ALWAYS (1 << 0)
+#define TRACE_CATEGORY_GRAPHICS (1 << 1)
+#define TRACE_CATEGORY_INPUT (1 << 2)
+#define TRACE_CATEGORY_VIEW (1 << 3)
+#define TRACE_CATEGORY_WEBVIEW (1 << 4)
+#define TRACE_CATEGORY_WINDOW_MANAGER (1 << 5)
+#define TRACE_CATEGORY_ACTIVITY_MANAGER (1 << 6)
+#define TRACE_CATEGORY_SYNC_MANAGER (1 << 7)
+#define TRACE_CATEGORY_AUDIO (1 << 8)
+#define TRACE_CATEGORY_VIDEO (1 << 9)
+#define TRACE_CATEGORY_CAMERA (1 << 10)
+#define TRACE_CATEGORY_HAL (1 << 11)
+#define TRACE_CATEGORY_APP (1 << 12)
+#define TRACE_CATEGORY_RESOURCES (1 << 13)
+#define TRACE_CATEGORY_DALVIK (1 << 14)
+#define TRACE_CATEGORY_RS (1 << 15)
+#define TRACE_CATEGORY_BIONIC (1 << 16)
+#define TRACE_CATEGORY_POWER (1 << 17)
+#define TRACE_CATEGORY_PACKAGE_MANAGER (1 << 18)
+#define TRACE_CATEGORY_SYSTEM_SERVER (1 << 19)
+#define TRACE_CATEGORY_DATABASE (1 << 20)
+#define TRACE_CATEGORY_NETWORK (1 << 21)
+#define TRACE_CATEGORY_ADB (1 << 22)
+#define TRACE_CATEGORY_VIBRATOR (1 << 23)
+#define TRACE_CATEGORY_AIDL (1 << 24)
+#define TRACE_CATEGORY_NNAPI (1 << 25)
+#define TRACE_CATEGORY_RRO (1 << 26)
+#define TRACE_CATEGORY_THERMAL (1 << 27)
+
+// Allow all categories except TRACE_CATEGORY_APP
+#define TRACE_CATEGORIES                                                      \
+  TRACE_CATEGORY_ALWAYS | TRACE_CATEGORY_GRAPHICS | TRACE_CATEGORY_INPUT |    \
+      TRACE_CATEGORY_VIEW | TRACE_CATEGORY_WEBVIEW |                          \
+      TRACE_CATEGORY_WINDOW_MANAGER | TRACE_CATEGORY_ACTIVITY_MANAGER |       \
+      TRACE_CATEGORY_SYNC_MANAGER | TRACE_CATEGORY_AUDIO |                    \
+      TRACE_CATEGORY_VIDEO | TRACE_CATEGORY_CAMERA | TRACE_CATEGORY_HAL |     \
+      TRACE_CATEGORY_RESOURCES | TRACE_CATEGORY_DALVIK | TRACE_CATEGORY_RS |  \
+      TRACE_CATEGORY_BIONIC | TRACE_CATEGORY_POWER |                          \
+      TRACE_CATEGORY_PACKAGE_MANAGER | TRACE_CATEGORY_SYSTEM_SERVER |         \
+      TRACE_CATEGORY_DATABASE | TRACE_CATEGORY_NETWORK | TRACE_CATEGORY_ADB | \
+      TRACE_CATEGORY_VIBRATOR | TRACE_CATEGORY_AIDL | TRACE_CATEGORY_NNAPI |  \
+      TRACE_CATEGORY_RRO | TRACE_CATEGORY_THERMAL
+#endif  // TRACE_CATEGORIES_H
diff --git a/libs/tracing_perfetto/include/tracing_perfetto.h b/libs/tracing_perfetto/include/tracing_perfetto.h
new file mode 100644
index 0000000..59c43d6
--- /dev/null
+++ b/libs/tracing_perfetto/include/tracing_perfetto.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test = false);
+
+void traceBegin(uint64_t category, const char* name);
+
+void traceEnd(uint64_t category);
+
+void traceAsyncBegin(uint64_t category, const char* name, int32_t cookie);
+
+void traceFormatBegin(uint64_t category, const char* fmt, ...);
+
+void traceAsyncEnd(uint64_t category, const char* name, int32_t cookie);
+
+void traceAsyncBeginForTrack(uint64_t category, const char* name,
+                               const char* trackName, int32_t cookie);
+
+void traceAsyncEndForTrack(uint64_t category, const char* trackName,
+                             int32_t cookie);
+
+void traceInstant(uint64_t category, const char* name);
+
+void traceFormatInstant(uint64_t category, const char* fmt, ...);
+
+void traceInstantForTrack(uint64_t category, const char* trackName,
+                            const char* name);
+
+void traceCounter(uint64_t category, const char* name, int64_t value);
+
+void traceCounter32(uint64_t category, const char* name, int32_t value);
+
+bool isTagEnabled(uint64_t category);
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tests/Android.bp b/libs/tracing_perfetto/tests/Android.bp
new file mode 100644
index 0000000..d203467
--- /dev/null
+++ b/libs/tracing_perfetto/tests/Android.bp
@@ -0,0 +1,48 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_test {
+    name: "libtracing_perfetto_tests",
+    static_libs: [
+        "libflagtest",
+        "libgmock",
+        "perfetto_trace_protos",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "android.os.flags-aconfig-cc-host",
+        "libbase",
+        "libperfetto_c",
+        "liblog",
+        "libprotobuf-cpp-lite",
+        "libtracing_perfetto",
+    ],
+    srcs: [
+        "tracing_perfetto_test.cpp",
+        "utils.cpp",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
new file mode 100644
index 0000000..e9fee2e
--- /dev/null
+++ b/libs/tracing_perfetto/tests/tracing_perfetto_test.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <android_os.h>
+#include <flag_macros.h>
+#include <thread>
+#include <unistd.h>
+
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/data_source_abi.h"
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/abi/tracing_session_abi.h"
+#include "perfetto/public/abi/track_event_abi.h"
+#include "perfetto/public/data_source.h"
+#include "perfetto/public/pb_decoder.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/trace/interned_data/interned_data.pzc.h"
+#include "perfetto/public/protos/trace/test_event.pzc.h"
+#include "perfetto/public/protos/trace/trace.pzc.h"
+#include "perfetto/public/protos/trace/trace_packet.pzc.h"
+#include "perfetto/public/protos/trace/track_event/debug_annotation.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_descriptor.pzc.h"
+#include "perfetto/public/protos/trace/track_event/track_event.pzc.h"
+#include "perfetto/public/protos/trace/trigger.pzc.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "utils.h"
+
+#include "protos/perfetto/trace/trace.pb.h"
+#include "protos/perfetto/trace/trace_packet.pb.h"
+#include "protos/perfetto/trace/interned_data/interned_data.pb.h"
+
+#include <fstream>
+#include <iterator>
+namespace tracing_perfetto {
+
+using ::perfetto::protos::Trace;
+using ::perfetto::protos::TracePacket;
+using ::perfetto::protos::EventCategory;
+using ::perfetto::protos::EventName;
+using ::perfetto::protos::FtraceEvent;
+using ::perfetto::protos::FtraceEventBundle;
+using ::perfetto::protos::InternedData;
+
+using ::perfetto::shlib::test_utils::TracingSession;
+
+const auto PERFETTO_SDK_TRACING = ACONFIG_FLAG(android::os, perfetto_sdk_tracing);
+
+// TODO(b/303199244): Add tests for all the library functions.
+class TracingPerfettoTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    tracing_perfetto::registerWithPerfetto(false /* test */);
+  }
+};
+
+Trace stopSession(TracingSession& tracing_session) {
+  tracing_session.FlushBlocking(5000);
+  tracing_session.StopBlocking();
+  std::vector<uint8_t> data = tracing_session.ReadBlocking();
+  std::string data_string(data.begin(), data.end());
+
+  perfetto::protos::Trace trace;
+  trace.ParseFromString(data_string);
+
+  return trace;
+}
+
+void verifyTrackEvent(const Trace& trace, const std::string expected_category,
+                      const std::string& expected_name) {
+  bool found = false;
+  for (const TracePacket& packet: trace.packet()) {
+    if (packet.has_track_event() && packet.has_interned_data()) {
+
+      const InternedData& interned_data = packet.interned_data();
+      if (interned_data.event_categories_size() > 0) {
+        const EventCategory& event_category = packet.interned_data().event_categories(0);
+        if (event_category.name() == expected_category) {
+          found = true;
+        }
+      }
+
+      if (interned_data.event_names_size() > 0) {
+        const EventName& event_name = packet.interned_data().event_names(0);
+        if (event_name.name() == expected_name) {
+          found &= true;
+        }
+      }
+
+      if (found) {
+        break;
+      }
+    }
+  }
+  EXPECT_TRUE(found);
+}
+
+void verifyAtraceEvent(const Trace& trace, const std::string& expected_name) {
+  std::string expected_print_buf = "I|" + std::to_string(gettid()) + "|" + expected_name + "\n";
+
+  bool found = false;
+  for (const TracePacket& packet: trace.packet()) {
+    if (packet.has_ftrace_events()) {
+      const FtraceEventBundle& ftrace_events_bundle = packet.ftrace_events();
+
+      if (ftrace_events_bundle.event_size() > 0) {
+        const FtraceEvent& ftrace_event = ftrace_events_bundle.event(0);
+        if (ftrace_event.has_print() && (ftrace_event.print().buf() == expected_print_buf)) {
+          found = true;
+          break;
+        }
+      }
+    }
+  }
+  EXPECT_TRUE(found);
+}
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstantWithPerfetto,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  std::string event_category = "input";
+  std::string event_name = "traceInstantWithPerfetto";
+
+  TracingSession tracing_session =
+      TracingSession::Builder().add_enabled_category(event_category).Build();
+
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, event_name.c_str());
+
+  Trace trace = stopSession(tracing_session);
+
+  verifyTrackEvent(trace, event_category, event_name);
+}
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstantWithAtrace,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  std::string event_category = "input";
+  std::string event_name = "traceInstantWithAtrace";
+
+  TracingSession tracing_session =
+      TracingSession::Builder().add_atrace_category(event_category).Build();
+
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, event_name.c_str());
+
+  Trace trace = stopSession(tracing_session);
+
+  verifyAtraceEvent(trace, event_name);
+}
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstantWithPerfettoAndAtrace,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  std::string event_category = "input";
+  std::string event_name = "traceInstantWithPerfettoAndAtrace";
+
+  TracingSession tracing_session =
+      TracingSession::Builder()
+      .add_atrace_category(event_category)
+      .add_enabled_category(event_category).Build();
+
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, event_name.c_str());
+
+  Trace trace = stopSession(tracing_session);
+
+  verifyAtraceEvent(trace, event_name);
+}
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstantWithPerfettoAndAtraceAndPreferTrackEvent,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  std::string event_category = "input";
+  std::string event_name = "traceInstantWithPerfettoAndAtraceAndPreferTrackEvent";
+
+  TracingSession tracing_session =
+      TracingSession::Builder()
+      .add_atrace_category(event_category)
+      .add_atrace_category_prefer_sdk(event_category)
+      .add_enabled_category(event_category).Build();
+
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, event_name.c_str());
+
+  Trace trace = stopSession(tracing_session);
+
+  verifyTrackEvent(trace, event_category, event_name);
+}
+
+TEST_F_WITH_FLAGS(TracingPerfettoTest, traceInstantWithPerfettoAndAtraceConcurrently,
+                  REQUIRES_FLAGS_ENABLED(PERFETTO_SDK_TRACING)) {
+  std::string event_category = "input";
+  std::string event_name = "traceInstantWithPerfettoAndAtraceConcurrently";
+
+  TracingSession perfetto_tracing_session =
+      TracingSession::Builder()
+      .add_atrace_category(event_category)
+      .add_atrace_category_prefer_sdk(event_category)
+      .add_enabled_category(event_category).Build();
+
+  TracingSession atrace_tracing_session =
+      TracingSession::Builder()
+      .add_atrace_category(event_category)
+      .add_enabled_category(event_category).Build();
+
+  tracing_perfetto::traceInstant(TRACE_CATEGORY_INPUT, event_name.c_str());
+
+  Trace atrace_trace = stopSession(atrace_tracing_session);
+  Trace perfetto_trace = stopSession(perfetto_tracing_session);
+
+  verifyAtraceEvent(atrace_trace, event_name);
+  verifyAtraceEvent(perfetto_trace, event_name);
+}
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tests/utils.cpp b/libs/tracing_perfetto/tests/utils.cpp
new file mode 100644
index 0000000..8c4d4a8
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.cc
+
+#include "utils.h"
+
+#include "perfetto/public/abi/heap_buffer.h"
+#include "perfetto/public/pb_msg.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/protos/config/data_source_config.pzc.h"
+#include "perfetto/public/protos/config/trace_config.pzc.h"
+#include "perfetto/public/protos/config/track_event/track_event_config.pzc.h"
+#include "perfetto/public/tracing_session.h"
+
+#include "protos/perfetto/config/ftrace/ftrace_config.pb.h"
+#include "protos/perfetto/config/track_event/track_event_config.pb.h"
+#include "protos/perfetto/config/data_source_config.pb.h"
+#include "protos/perfetto/config/trace_config.pb.h"
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+namespace {
+
+std::string ToHexChars(uint8_t val) {
+  std::string ret;
+  uint8_t high_nibble = (val & 0xF0) >> 4;
+  uint8_t low_nibble = (val & 0xF);
+  static const char hex_chars[] = "0123456789ABCDEF";
+  ret.push_back(hex_chars[high_nibble]);
+  ret.push_back(hex_chars[low_nibble]);
+  return ret;
+}
+
+}  // namespace
+
+TracingSession TracingSession::Builder::Build() {
+  perfetto::protos::TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(1024);
+
+  auto* track_event_ds_config = trace_config.add_data_sources()->mutable_config();
+  auto* ftrace_ds_config = trace_config.add_data_sources()->mutable_config();
+
+  track_event_ds_config->set_name("track_event");
+  track_event_ds_config->set_target_buffer(0);
+
+  ftrace_ds_config->set_name("linux.ftrace");
+  ftrace_ds_config->set_target_buffer(0);
+
+  {
+    auto* ftrace_config = ftrace_ds_config->mutable_ftrace_config();
+    if (!atrace_categories_.empty()) {
+      ftrace_config->add_ftrace_events("ftrace/print");
+      for (const std::string& cat : atrace_categories_) {
+        ftrace_config->add_atrace_categories(cat);
+      }
+
+      for (const std::string& cat : atrace_categories_prefer_sdk_) {
+        ftrace_config->add_atrace_categories_prefer_sdk(cat);
+      }
+    }
+  }
+
+  {
+    auto* track_event_config = track_event_ds_config->mutable_track_event_config();
+    if (!enabled_categories_.empty() || !disabled_categories_.empty()) {
+      for (const std::string& cat : enabled_categories_) {
+        track_event_config->add_enabled_categories(cat);
+      }
+
+      for (const std::string& cat : disabled_categories_) {
+        track_event_config->add_disabled_categories(cat);
+      }
+    }
+  }
+
+  struct PerfettoTracingSessionImpl* ts =
+      PerfettoTracingSessionCreate(PERFETTO_BACKEND_SYSTEM);
+
+  std::string trace_config_string;
+  trace_config.SerializeToString(&trace_config_string);
+
+  PerfettoTracingSessionSetup(ts, trace_config_string.data(), trace_config_string.length());
+
+  // Fails to start here
+  PerfettoTracingSessionStartBlocking(ts);
+
+  return TracingSession::Adopt(ts);
+}
+
+TracingSession TracingSession::Adopt(struct PerfettoTracingSessionImpl* session) {
+  TracingSession ret;
+  ret.session_ = session;
+  ret.stopped_ = std::make_unique<WaitableEvent>();
+  PerfettoTracingSessionSetStopCb(
+      ret.session_,
+      [](struct PerfettoTracingSessionImpl*, void* arg) {
+        static_cast<WaitableEvent*>(arg)->Notify();
+      },
+      ret.stopped_.get());
+  return ret;
+}
+
+TracingSession::TracingSession(TracingSession&& other) noexcept {
+  session_ = other.session_;
+  other.session_ = nullptr;
+  stopped_ = std::move(other.stopped_);
+  other.stopped_ = nullptr;
+}
+
+TracingSession::~TracingSession() {
+  if (!session_) {
+    return;
+  }
+  if (!stopped_->IsNotified()) {
+    PerfettoTracingSessionStopBlocking(session_);
+    stopped_->WaitForNotification();
+  }
+  PerfettoTracingSessionDestroy(session_);
+}
+
+bool TracingSession::FlushBlocking(uint32_t timeout_ms) {
+  WaitableEvent notification;
+  bool result;
+  auto* cb = new std::function<void(bool)>([&](bool success) {
+    result = success;
+    notification.Notify();
+  });
+  PerfettoTracingSessionFlushAsync(
+      session_, timeout_ms,
+      [](PerfettoTracingSessionImpl*, bool success, void* user_arg) {
+        auto* f = reinterpret_cast<std::function<void(bool)>*>(user_arg);
+        (*f)(success);
+        delete f;
+      },
+      cb);
+  notification.WaitForNotification();
+  return result;
+}
+
+void TracingSession::WaitForStopped() {
+  stopped_->WaitForNotification();
+}
+
+void TracingSession::StopBlocking() {
+  PerfettoTracingSessionStopBlocking(session_);
+}
+
+std::vector<uint8_t> TracingSession::ReadBlocking() {
+  std::vector<uint8_t> data;
+  PerfettoTracingSessionReadTraceBlocking(
+      session_,
+      [](struct PerfettoTracingSessionImpl*, const void* trace_data,
+         size_t size, bool, void* user_arg) {
+        auto& dst = *static_cast<std::vector<uint8_t>*>(user_arg);
+        auto* src = static_cast<const uint8_t*>(trace_data);
+        dst.insert(dst.end(), src, src + size);
+      },
+      &data);
+  return data;
+}
+
+}  // namespace test_utils
+}  // namespace shlib
+}  // namespace perfetto
+
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream* pos) {
+  std::ostream& os = *pos;
+  PerfettoPbDecoderStatus status =
+      static_cast<PerfettoPbDecoderStatus>(field.status);
+  switch (status) {
+    case PERFETTO_PB_DECODER_ERROR:
+      os << "MALFORMED PROTOBUF";
+      break;
+    case PERFETTO_PB_DECODER_DONE:
+      os << "DECODER DONE";
+      break;
+    case PERFETTO_PB_DECODER_OK:
+      switch (field.wire_type) {
+        case PERFETTO_PB_WIRE_TYPE_DELIMITED:
+          os << "\"";
+          for (size_t i = 0; i < field.value.delimited.len; i++) {
+            os << perfetto::shlib::test_utils::ToHexChars(
+                      field.value.delimited.start[i])
+               << " ";
+          }
+          os << "\"";
+          break;
+        case PERFETTO_PB_WIRE_TYPE_VARINT:
+          os << "varint: " << field.value.integer64;
+          break;
+        case PERFETTO_PB_WIRE_TYPE_FIXED32:
+          os << "fixed32: " << field.value.integer32;
+          break;
+        case PERFETTO_PB_WIRE_TYPE_FIXED64:
+          os << "fixed64: " << field.value.integer64;
+          break;
+      }
+      break;
+  }
+}
diff --git a/libs/tracing_perfetto/tests/utils.h b/libs/tracing_perfetto/tests/utils.h
new file mode 100644
index 0000000..8edb414
--- /dev/null
+++ b/libs/tracing_perfetto/tests/utils.h
@@ -0,0 +1,457 @@
+/*
+ * Copyright 2024 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.
+ */
+
+// Copied from //external/perfetto/src/shared_lib/test/utils.h
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <cassert>
+#include <condition_variable>
+#include <cstdint>
+#include <functional>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock-matchers.h"
+#include "gmock/gmock-more-matchers.h"
+#include "gtest/gtest-matchers.h"
+#include "gtest/gtest.h"
+#include "perfetto/public/abi/pb_decoder_abi.h"
+#include "perfetto/public/pb_utils.h"
+#include "perfetto/public/tracing_session.h"
+
+// Pretty printer for gtest
+void PrintTo(const PerfettoPbDecoderField& field, std::ostream*);
+
+namespace perfetto {
+namespace shlib {
+namespace test_utils {
+
+class WaitableEvent {
+ public:
+  WaitableEvent() = default;
+  void Notify() {
+    std::unique_lock<std::mutex> lock(m_);
+    notified_ = true;
+    cv_.notify_one();
+  }
+  bool WaitForNotification() {
+    std::unique_lock<std::mutex> lock(m_);
+    cv_.wait(lock, [this] { return notified_; });
+    return notified_;
+  }
+  bool IsNotified() {
+    std::unique_lock<std::mutex> lock(m_);
+    return notified_;
+  }
+
+ private:
+  std::mutex m_;
+  std::condition_variable cv_;
+  bool notified_ = false;
+};
+
+class TracingSession {
+ public:
+  class Builder {
+   public:
+    Builder() = default;
+    Builder& add_enabled_category(std::string category) {
+      enabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_disabled_category(std::string category) {
+      disabled_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_atrace_category(std::string category) {
+      atrace_categories_.push_back(std::move(category));
+      return *this;
+    }
+    Builder& add_atrace_category_prefer_sdk(std::string category) {
+      atrace_categories_prefer_sdk_.push_back(std::move(category));
+      return *this;
+    }
+    TracingSession Build();
+
+   private:
+    std::vector<std::string> enabled_categories_;
+    std::vector<std::string> disabled_categories_;
+    std::vector<std::string> atrace_categories_;
+    std::vector<std::string> atrace_categories_prefer_sdk_;
+  };
+
+  static TracingSession Adopt(struct PerfettoTracingSessionImpl*);
+
+  TracingSession(TracingSession&&) noexcept;
+
+  ~TracingSession();
+
+  struct PerfettoTracingSessionImpl* session() const {
+    return session_;
+  }
+
+  bool FlushBlocking(uint32_t timeout_ms);
+  void WaitForStopped();
+  void StopBlocking();
+  std::vector<uint8_t> ReadBlocking();
+
+ private:
+  TracingSession() = default;
+  struct PerfettoTracingSessionImpl* session_;
+  std::unique_ptr<WaitableEvent> stopped_;
+};
+
+template <typename FieldSkipper>
+class FieldViewBase {
+ public:
+  class Iterator {
+   public:
+    using iterator_category = std::input_iterator_tag;
+    using value_type = const PerfettoPbDecoderField;
+    using pointer = value_type;
+    using reference = value_type;
+    reference operator*() const {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      struct PerfettoPbDecoderField field;
+      do {
+        field = PerfettoPbDecoderParseField(&decoder);
+      } while (field.status == PERFETTO_PB_DECODER_OK &&
+               skipper_.ShouldSkip(field));
+      return field;
+    }
+    Iterator& operator++() {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      PerfettoPbDecoderSkipField(&decoder);
+      read_ptr_ = decoder.read_ptr;
+      AdvanceToFirstInterestingField();
+      return *this;
+    }
+    Iterator operator++(int) {
+      Iterator tmp = *this;
+      ++(*this);
+      return tmp;
+    }
+
+    friend bool operator==(const Iterator& a, const Iterator& b) {
+      return a.read_ptr_ == b.read_ptr_;
+    }
+    friend bool operator!=(const Iterator& a, const Iterator& b) {
+      return a.read_ptr_ != b.read_ptr_;
+    }
+
+   private:
+    Iterator(const uint8_t* read_ptr, const uint8_t* end_ptr,
+             const FieldSkipper& skipper)
+        : read_ptr_(read_ptr), end_ptr_(end_ptr), skipper_(skipper) {
+      AdvanceToFirstInterestingField();
+    }
+    void AdvanceToFirstInterestingField() {
+      struct PerfettoPbDecoder decoder;
+      decoder.read_ptr = read_ptr_;
+      decoder.end_ptr = end_ptr_;
+      struct PerfettoPbDecoderField field;
+      const uint8_t* prev_read_ptr;
+      do {
+        prev_read_ptr = decoder.read_ptr;
+        field = PerfettoPbDecoderParseField(&decoder);
+      } while (field.status == PERFETTO_PB_DECODER_OK &&
+               skipper_.ShouldSkip(field));
+      if (field.status == PERFETTO_PB_DECODER_OK) {
+        read_ptr_ = prev_read_ptr;
+      } else {
+        read_ptr_ = decoder.read_ptr;
+      }
+    }
+    friend class FieldViewBase<FieldSkipper>;
+    const uint8_t* read_ptr_;
+    const uint8_t* end_ptr_;
+    const FieldSkipper& skipper_;
+  };
+  using value_type = const PerfettoPbDecoderField;
+  using const_iterator = Iterator;
+  template <typename... Args>
+  explicit FieldViewBase(const uint8_t* begin, const uint8_t* end, Args... args)
+      : begin_(begin), end_(end), s_(args...) {
+  }
+  template <typename... Args>
+  explicit FieldViewBase(const std::vector<uint8_t>& data, Args... args)
+      : FieldViewBase(data.data(), data.data() + data.size(), args...) {
+  }
+  template <typename... Args>
+  explicit FieldViewBase(const struct PerfettoPbDecoderField& field,
+                         Args... args)
+      : s_(args...) {
+    if (field.wire_type != PERFETTO_PB_WIRE_TYPE_DELIMITED) {
+      abort();
+    }
+    begin_ = field.value.delimited.start;
+    end_ = begin_ + field.value.delimited.len;
+  }
+  Iterator begin() const {
+    return Iterator(begin_, end_, s_);
+  }
+  Iterator end() const {
+    return Iterator(end_, end_, s_);
+  }
+  PerfettoPbDecoderField front() const {
+    return *begin();
+  }
+
+  size_t size() const {
+    size_t count = 0;
+    for (auto field : *this) {
+      (void)field;
+      count++;
+    }
+    return count;
+  }
+
+  bool ok() const {
+    for (auto field : *this) {
+      if (field.status != PERFETTO_PB_DECODER_OK) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
+  const uint8_t* begin_;
+  const uint8_t* end_;
+  FieldSkipper s_;
+};
+
+// Pretty printer for gtest
+template <typename FieldSkipper>
+void PrintTo(const FieldViewBase<FieldSkipper>& field_view, std::ostream* pos) {
+  std::ostream& os = *pos;
+  os << "{";
+  for (PerfettoPbDecoderField f : field_view) {
+    PrintTo(f, pos);
+    os << ", ";
+  }
+  os << "}";
+}
+
+class IdFieldSkipper {
+ public:
+  explicit IdFieldSkipper(uint32_t id) : id_(id) {
+  }
+  explicit IdFieldSkipper(int32_t id) : id_(static_cast<uint32_t>(id)) {
+  }
+  bool ShouldSkip(const struct PerfettoPbDecoderField& field) const {
+    return field.id != id_;
+  }
+
+ private:
+  uint32_t id_;
+};
+
+class NoFieldSkipper {
+ public:
+  NoFieldSkipper() = default;
+  bool ShouldSkip(const struct PerfettoPbDecoderField&) const {
+    return false;
+  }
+};
+
+// View over all the fields of a contiguous serialized protobuf message.
+//
+// Examples:
+//
+// for (struct PerfettoPbDecoderField field : FieldView(msg_begin, msg_end)) {
+//   //...
+// }
+// FieldView fields2(/*PerfettoPbDecoderField*/ nested_field);
+// FieldView fields3(/*std::vector<uint8_t>*/ data);
+// size_t num = fields1.size(); // The number of fields.
+// bool ok = fields1.ok(); // Checks that the message is not malformed.
+using FieldView = FieldViewBase<NoFieldSkipper>;
+
+// Like `FieldView`, but only considers fields with a specific id.
+//
+// Examples:
+//
+// IdFieldView fields(msg_begin, msg_end, id)
+using IdFieldView = FieldViewBase<IdFieldSkipper>;
+
+// Matches a PerfettoPbDecoderField with the specified id. Accepts another
+// matcher to match the contents of the field.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, PbField(900, VarIntField(5)));
+template <typename M>
+auto PbField(int32_t id, M m) {
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::id, id), m);
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, MsgField(ElementsAre(...)));
+template <typename M>
+auto MsgField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) { return FieldView(field); };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField length delimited field. Accepts a string
+// matcher.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, StringField("string"));
+template <typename M>
+auto StringField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return std::string(
+        reinterpret_cast<const char*>(field.value.delimited.start),
+        field.value.delimited.len);
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField VarInt field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, VarIntField(1)));
+template <typename M>
+auto VarIntField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer64;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_VARINT),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed64 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed64Field(1)));
+template <typename M>
+auto Fixed64Field(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer64;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED64),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField fixed32 field. Accepts an integer matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, Fixed32Field(1)));
+template <typename M>
+auto Fixed32Field(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.integer32;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED32),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField double field. Accepts a double matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, DoubleField(1.0)));
+template <typename M>
+auto DoubleField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.double_val;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED64),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField float field. Accepts a float matcher
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, FloatField(1.0)));
+template <typename M>
+auto FloatField(M m) {
+  auto f = [](const PerfettoPbDecoderField& field) {
+    return field.value.float_val;
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_FIXED32),
+      testing::ResultOf(f, m));
+}
+
+// Matches a PerfettoPbDecoderField submessage field. Accepts a container
+// matcher for the subfields.
+//
+// Example:
+// PerfettoPbDecoderField field = ...
+// EXPECT_THAT(field, AllFieldsWithId(900, ElementsAre(...)));
+template <typename M>
+auto AllFieldsWithId(int32_t id, M m) {
+  auto f = [id](const PerfettoPbDecoderField& field) {
+    return IdFieldView(field, id);
+  };
+  return testing::AllOf(
+      testing::Field(&PerfettoPbDecoderField::status, PERFETTO_PB_DECODER_OK),
+      testing::Field(&PerfettoPbDecoderField::wire_type,
+                     PERFETTO_PB_WIRE_TYPE_DELIMITED),
+      testing::ResultOf(f, m));
+}
+
+}  // namespace test_utils
+}  // namespace shlib
+}  // namespace perfetto
+
+#endif  // UTILS_H
diff --git a/libs/tracing_perfetto/tracing_perfetto.cpp b/libs/tracing_perfetto/tracing_perfetto.cpp
new file mode 100644
index 0000000..c35e078
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2024 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 "tracing_perfetto.h"
+
+#include <cutils/trace.h>
+#include <cstdarg>
+
+#include "perfetto/public/te_category_macros.h"
+#include "trace_categories.h"
+#include "tracing_perfetto_internal.h"
+
+namespace tracing_perfetto {
+
+void registerWithPerfetto(bool test) {
+  internal::registerWithPerfetto(test);
+}
+
+void traceBegin(uint64_t category, const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_begin(category, name);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceBegin(*perfettoTeCategory, name);
+  }
+}
+
+void traceFormatBegin(uint64_t category, const char* fmt, ...) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  const bool preferAtrace = internal::shouldPreferAtrace(perfettoTeCategory, category);
+  const bool preferPerfetto = internal::isPerfettoCategoryEnabled(perfettoTeCategory);
+  if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
+    return;
+  }
+
+  const int BUFFER_SIZE = 256;
+  va_list ap;
+  char buf[BUFFER_SIZE];
+
+  va_start(ap, fmt);
+  vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+  va_end(ap);
+
+
+  if (preferAtrace) {
+    atrace_begin(category, buf);
+  } else if (preferPerfetto) {
+    internal::perfettoTraceBegin(*perfettoTeCategory, buf);
+  }
+}
+
+void traceEnd(uint64_t category) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_end(category);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceEnd(*perfettoTeCategory);
+  }
+}
+
+void traceAsyncBegin(uint64_t category, const char* name, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_async_begin(category, name, cookie);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncBegin(*perfettoTeCategory, name, cookie);
+  }
+}
+
+void traceAsyncEnd(uint64_t category, const char* name, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_async_end(category, name, cookie);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncEnd(*perfettoTeCategory, name, cookie);
+  }
+}
+
+void traceAsyncBeginForTrack(uint64_t category, const char* name,
+                               const char* trackName, int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_async_for_track_begin(category, trackName, name, cookie);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncBeginForTrack(*perfettoTeCategory, name, trackName, cookie);
+  }
+}
+
+void traceAsyncEndForTrack(uint64_t category, const char* trackName,
+                             int32_t cookie) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_async_for_track_end(category, trackName, cookie);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceAsyncEndForTrack(*perfettoTeCategory, trackName, cookie);
+  }
+}
+
+void traceInstant(uint64_t category, const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_instant(category, name);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceInstant(*perfettoTeCategory, name);
+  }
+}
+
+void traceFormatInstant(uint64_t category, const char* fmt, ...) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  const bool preferAtrace = internal::shouldPreferAtrace(perfettoTeCategory, category);
+  const bool preferPerfetto = internal::isPerfettoCategoryEnabled(perfettoTeCategory);
+  if (CC_LIKELY(!(preferAtrace || preferPerfetto))) {
+    return;
+  }
+
+  const int BUFFER_SIZE = 256;
+  va_list ap;
+  char buf[BUFFER_SIZE];
+
+  va_start(ap, fmt);
+  vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+  va_end(ap);
+
+  if (preferAtrace) {
+    atrace_instant(category, buf);
+  } else if (preferPerfetto) {
+    internal::perfettoTraceInstant(*perfettoTeCategory, buf);
+  }
+}
+
+void traceInstantForTrack(uint64_t category, const char* trackName,
+                            const char* name) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_instant_for_track(category, trackName, name);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceInstantForTrack(*perfettoTeCategory, trackName, name);
+  }
+}
+
+void traceCounter(uint64_t category, const char* name, int64_t value) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_int64(category, name, value);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceCounter(*perfettoTeCategory, name, value);
+  }
+}
+
+void traceCounter32(uint64_t category, const char* name, int32_t value) {
+  struct PerfettoTeCategory* perfettoTeCategory = internal::toPerfettoCategory(category);
+  if (internal::shouldPreferAtrace(perfettoTeCategory, category)) {
+    atrace_int(category, name, value);
+  } else if (internal::isPerfettoCategoryEnabled(perfettoTeCategory)) {
+    internal::perfettoTraceCounter(*perfettoTeCategory, name,
+                                          static_cast<int64_t>(value));
+  }
+}
+
+bool isTagEnabled(uint64_t category) {
+  struct PerfettoTeCategory* perfettoTeCategory =
+      internal::toPerfettoCategory(category);
+  return internal::isPerfettoCategoryEnabled(perfettoTeCategory)
+      || atrace_is_tag_enabled(category);
+}
+
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.cpp b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
new file mode 100644
index 0000000..9a0042a
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.cpp
@@ -0,0 +1,295 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define FRAMEWORK_CATEGORIES(C)                                  \
+  C(always, "always", "Always category")                         \
+  C(graphics, "graphics", "Graphics category")                   \
+  C(input, "input", "Input category")                            \
+  C(view, "view", "View category")                               \
+  C(webview, "webview", "WebView category")                      \
+  C(windowmanager, "wm", "WindowManager category")               \
+  C(activitymanager, "am", "ActivityManager category")           \
+  C(syncmanager, "syncmanager", "SyncManager category")          \
+  C(audio, "audio", "Audio category")                            \
+  C(video, "video", "Video category")                            \
+  C(camera, "camera", "Camera category")                         \
+  C(hal, "hal", "HAL category")                                  \
+  C(app, "app", "App category")                                  \
+  C(resources, "res", "Resources category")                      \
+  C(dalvik, "dalvik", "Dalvik category")                         \
+  C(rs, "rs", "RS category")                                     \
+  C(bionic, "bionic", "Bionic category")                         \
+  C(power, "power", "Power category")                            \
+  C(packagemanager, "packagemanager", "PackageManager category") \
+  C(systemserver, "ss", "System Server category")                \
+  C(database, "database", "Database category")                   \
+  C(network, "network", "Network category")                      \
+  C(adb, "adb", "ADB category")                                  \
+  C(vibrator, "vibrator", "Vibrator category")                   \
+  C(aidl, "aidl", "AIDL category")                               \
+  C(nnapi, "nnapi", "NNAPI category")                            \
+  C(rro, "rro", "RRO category")                                  \
+  C(thermal, "thermal", "Thermal category")
+
+#include <atomic>
+#include <mutex>
+
+#include <android_os.h>
+#include <android-base/properties.h>
+#include <cutils/trace.h>
+#include <inttypes.h>
+
+#include "perfetto/public/compiler.h"
+#include "perfetto/public/producer.h"
+#include "perfetto/public/te_category_macros.h"
+#include "perfetto/public/te_macros.h"
+#include "perfetto/public/track_event.h"
+#include "trace_categories.h"
+#include "tracing_perfetto_internal.h"
+
+#ifdef __BIONIC__
+#define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_
+#include <sys/_system_properties.h>
+#endif
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+namespace {
+PERFETTO_TE_CATEGORIES_DECLARE(FRAMEWORK_CATEGORIES);
+
+PERFETTO_TE_CATEGORIES_DEFINE(FRAMEWORK_CATEGORIES);
+
+static constexpr char kPreferFlagProperty[] = "debug.atrace.prefer_sdk";
+static std::atomic<const prop_info*> prefer_property_info = nullptr;
+static std::atomic_uint32_t last_prefer_seq_num = 0;
+static std::atomic_uint64_t prefer_flags = 0;
+
+static const prop_info* system_property_find(const char* name [[maybe_unused]]) {
+  #ifdef __BIONIC__
+  return __system_property_find(name);
+  #endif
+
+  return nullptr;
+}
+
+static uint32_t system_property_serial(const prop_info* pi [[maybe_unused]]) {
+  #ifdef __BIONIC__
+  return __system_property_serial(pi);
+  #endif
+
+  return last_prefer_seq_num;
+}
+
+struct PerfettoTeCategory* toCategory(uint64_t inCategory) {
+  switch (inCategory) {
+    case TRACE_CATEGORY_ALWAYS:
+      return &always;
+    case TRACE_CATEGORY_GRAPHICS:
+      return &graphics;
+    case TRACE_CATEGORY_INPUT:
+      return &input;
+    case TRACE_CATEGORY_VIEW:
+      return &view;
+    case TRACE_CATEGORY_WEBVIEW:
+      return &webview;
+    case TRACE_CATEGORY_WINDOW_MANAGER:
+      return &windowmanager;
+    case TRACE_CATEGORY_ACTIVITY_MANAGER:
+      return &activitymanager;
+    case TRACE_CATEGORY_SYNC_MANAGER:
+      return &syncmanager;
+    case TRACE_CATEGORY_AUDIO:
+      return &audio;
+    case TRACE_CATEGORY_VIDEO:
+      return &video;
+    case TRACE_CATEGORY_CAMERA:
+      return &camera;
+    case TRACE_CATEGORY_HAL:
+      return &hal;
+    case TRACE_CATEGORY_APP:
+      return &app;
+    case TRACE_CATEGORY_RESOURCES:
+      return &resources;
+    case TRACE_CATEGORY_DALVIK:
+      return &dalvik;
+    case TRACE_CATEGORY_RS:
+      return &rs;
+    case TRACE_CATEGORY_BIONIC:
+      return &bionic;
+    case TRACE_CATEGORY_POWER:
+      return &power;
+    case TRACE_CATEGORY_PACKAGE_MANAGER:
+      return &packagemanager;
+    case TRACE_CATEGORY_SYSTEM_SERVER:
+      return &systemserver;
+    case TRACE_CATEGORY_DATABASE:
+      return &database;
+    case TRACE_CATEGORY_NETWORK:
+      return &network;
+    case TRACE_CATEGORY_ADB:
+      return &adb;
+    case TRACE_CATEGORY_VIBRATOR:
+      return &vibrator;
+    case TRACE_CATEGORY_AIDL:
+      return &aidl;
+    case TRACE_CATEGORY_NNAPI:
+      return &nnapi;
+    case TRACE_CATEGORY_RRO:
+      return &rro;
+    case TRACE_CATEGORY_THERMAL:
+      return &thermal;
+    default:
+      return nullptr;
+  }
+}
+
+}  // namespace
+
+bool isPerfettoCategoryEnabled(PerfettoTeCategory* category) {
+  return category != nullptr;
+}
+
+/**
+ * Updates the cached |prefer_flags|.
+ *
+ * We cache the prefer_flags because reading it on every trace event is expensive.
+ * The cache is invalidated when a sys_prop sequence number changes.
+ */
+void updatePreferFlags() {
+  if (!prefer_property_info.load(std::memory_order_acquire)) {
+    auto* new_prefer_property_info = system_property_find(kPreferFlagProperty);
+    prefer_flags.store(android::base::GetIntProperty(kPreferFlagProperty, 0),
+                       std::memory_order_relaxed);
+
+    if (!new_prefer_property_info) {
+      // This should never happen. If it does, we fail gracefully and end up reading the property
+      // traced event.
+      return;
+    }
+
+    last_prefer_seq_num = system_property_serial(new_prefer_property_info);
+    prefer_property_info.store(new_prefer_property_info, std::memory_order_release);
+  }
+
+  uint32_t prefer_seq_num =  system_property_serial(prefer_property_info);
+  if (prefer_seq_num != last_prefer_seq_num.load(std::memory_order_acquire)) {
+    prefer_flags.store(android::base::GetIntProperty(kPreferFlagProperty, 0),
+                       std::memory_order_relaxed);
+    last_prefer_seq_num.store(prefer_seq_num, std::memory_order_release);
+  }
+}
+
+bool shouldPreferAtrace(PerfettoTeCategory *perfettoCategory, uint64_t atraceCategory) {
+  // There are 3 cases:
+  // 1. Atrace is not enabled.
+  if (!atrace_is_tag_enabled(atraceCategory)) {
+    return false;
+  }
+
+  // 2. Atrace is enabled but perfetto is not enabled.
+  if (!isPerfettoCategoryEnabled(perfettoCategory)) {
+    return true;
+  }
+
+  // Update prefer_flags before checking it below
+  updatePreferFlags();
+
+  // 3. Atrace and perfetto are enabled.
+  // Even though this category is enabled for track events, the config mandates that we downgrade
+  // it to atrace if the same atrace category is currently enabled. This prevents missing the
+  // event from a concurrent session that needs the same category in atrace.
+  return (atraceCategory & prefer_flags.load(std::memory_order_relaxed)) == 0;
+}
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category) {
+  struct PerfettoTeCategory* perfettoCategory = toCategory(category);
+  if (perfettoCategory == nullptr) {
+    return nullptr;
+  }
+
+  bool enabled = PERFETTO_UNLIKELY(PERFETTO_ATOMIC_LOAD_EXPLICIT(
+       (*perfettoCategory).enabled, PERFETTO_MEMORY_ORDER_RELAXED));
+  return enabled ? perfettoCategory : nullptr;
+}
+
+void registerWithPerfetto(bool test) {
+  if (!android::os::perfetto_sdk_tracing()) {
+    return;
+  }
+
+  static std::once_flag registration;
+  std::call_once(registration, [test]() {
+    struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
+    args.backends = test ? PERFETTO_BACKEND_IN_PROCESS : PERFETTO_BACKEND_SYSTEM;
+    PerfettoProducerInit(args);
+    PerfettoTeInit();
+    PERFETTO_TE_REGISTER_CATEGORIES(FRAMEWORK_CATEGORIES);
+  });
+}
+
+void perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name) {
+  PERFETTO_TE(category, PERFETTO_TE_SLICE_BEGIN(name));
+}
+
+void perfettoTraceEnd(const struct PerfettoTeCategory& category) {
+  PERFETTO_TE(category, PERFETTO_TE_SLICE_END());
+}
+
+void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+                                       const char* trackName, uint64_t cookie) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_SLICE_BEGIN(name),
+      PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+}
+
+void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+                                     const char* trackName, uint64_t cookie) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_SLICE_END(),
+      PERFETTO_TE_NAMED_TRACK(trackName, cookie, PerfettoTeProcessTrackUuid()));
+}
+
+void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+                               uint64_t cookie) {
+  perfettoTraceAsyncBeginForTrack(category, name, name, cookie);
+}
+
+void perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+                             uint64_t cookie) {
+  perfettoTraceAsyncEndForTrack(category, name, cookie);
+}
+
+void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name) {
+  PERFETTO_TE(category, PERFETTO_TE_INSTANT(name));
+}
+
+void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+                                    const char* trackName, const char* name) {
+  PERFETTO_TE(
+      category, PERFETTO_TE_INSTANT(name),
+      PERFETTO_TE_NAMED_TRACK(trackName, 1, PerfettoTeProcessTrackUuid()));
+}
+
+void perfettoTraceCounter(const struct PerfettoTeCategory& category,
+                            [[maybe_unused]] const char* name, int64_t value) {
+  PERFETTO_TE(category, PERFETTO_TE_COUNTER(),
+              PERFETTO_TE_INT_COUNTER(value));
+}
+}  // namespace internal
+
+}  // namespace tracing_perfetto
diff --git a/libs/tracing_perfetto/tracing_perfetto_internal.h b/libs/tracing_perfetto/tracing_perfetto_internal.h
new file mode 100644
index 0000000..3e1ac2a
--- /dev/null
+++ b/libs/tracing_perfetto/tracing_perfetto_internal.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2024 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 TRACING_PERFETTO_INTERNAL_H
+#define TRACING_PERFETTO_INTERNAL_H
+
+#include <stdint.h>
+
+#include "perfetto/public/te_category_macros.h"
+
+namespace tracing_perfetto {
+
+namespace internal {
+
+bool isPerfettoRegistered();
+
+struct PerfettoTeCategory* toPerfettoCategory(uint64_t category);
+
+void registerWithPerfetto(bool test = false);
+
+void perfettoTraceBegin(const struct PerfettoTeCategory& category, const char* name);
+
+void perfettoTraceEnd(const struct PerfettoTeCategory& category);
+
+void perfettoTraceAsyncBegin(const struct PerfettoTeCategory& category, const char* name,
+                               uint64_t cookie);
+
+void perfettoTraceAsyncEnd(const struct PerfettoTeCategory& category, const char* name,
+                             uint64_t cookie);
+
+void perfettoTraceAsyncBeginForTrack(const struct PerfettoTeCategory& category, const char* name,
+                                       const char* trackName, uint64_t cookie);
+
+void perfettoTraceAsyncEndForTrack(const struct PerfettoTeCategory& category,
+                                     const char* trackName, uint64_t cookie);
+
+void perfettoTraceInstant(const struct PerfettoTeCategory& category, const char* name);
+
+void perfettoTraceInstantForTrack(const struct PerfettoTeCategory& category,
+                                    const char* trackName, const char* name);
+
+void perfettoTraceCounter(const struct PerfettoTeCategory& category, const char* name,
+                            int64_t value);
+
+bool isPerfettoCategoryEnabled(PerfettoTeCategory *perfettoTeCategory);
+
+bool shouldPreferAtrace(PerfettoTeCategory *perfettoTeCategory, uint64_t category);
+
+}  // namespace internal
+
+}  // namespace tracing_perfetto
+
+#endif  // TRACING_PERFETTO_INTERNAL_H
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 312a1e6..12230f9 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -277,13 +277,3 @@
     "tests",
     "tools",
 ]
-
-filegroup {
-    name: "libui_host_common",
-    srcs: [
-        "Rect.cpp",
-        "Region.cpp",
-        "PixelFormat.cpp",
-        "Transform.cpp",
-    ],
-}
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 8675f14..bee58e5 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -22,14 +22,12 @@
 #include <android-base/stringprintf.h>
 #include <string>
 
-using android::base::StringAppendF;
 using android::base::StringPrintf;
 using android::ui::ColorMode;
 using android::ui::RenderIntent;
 
-std::string decodeStandard(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    switch (dataspaceSelect) {
+std::string decodeStandardOnly(uint32_t dataspaceStandard) {
+    switch (dataspaceStandard) {
         case HAL_DATASPACE_STANDARD_BT709:
             return std::string("BT709");
 
@@ -62,63 +60,44 @@
 
         case HAL_DATASPACE_STANDARD_ADOBE_RGB:
             return std::string("AdobeRGB");
-
-        case 0:
-            switch (dataspace & 0xffff) {
-                case HAL_DATASPACE_JFIF:
-                    return std::string("(deprecated) JFIF (BT601_625)");
-
-                case HAL_DATASPACE_BT601_625:
-                    return std::string("(deprecated) BT601_625");
-
-                case HAL_DATASPACE_BT601_525:
-                    return std::string("(deprecated) BT601_525");
-
-                case HAL_DATASPACE_SRGB_LINEAR:
-                case HAL_DATASPACE_SRGB:
-                    return std::string("(deprecated) sRGB");
-
-                case HAL_DATASPACE_BT709:
-                    return std::string("(deprecated) BT709");
-
-                case HAL_DATASPACE_ARBITRARY:
-                    return std::string("ARBITRARY");
-
-                case HAL_DATASPACE_UNKNOWN:
-                // Fallthrough
-                default:
-                    return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
-            }
     }
 
-    return StringPrintf("Unknown dataspace code %d", dataspaceSelect);
+    return StringPrintf("Unknown dataspace code %d", dataspaceStandard);
 }
 
-std::string decodeTransfer(android_dataspace dataspace) {
-    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
-    if (dataspaceSelect == 0) {
+std::string decodeStandard(android_dataspace dataspace) {
+    const uint32_t dataspaceStandard = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceStandard == 0) {
         switch (dataspace & 0xffff) {
             case HAL_DATASPACE_JFIF:
+                return std::string("(deprecated) JFIF (BT601_625)");
+
             case HAL_DATASPACE_BT601_625:
+                return std::string("(deprecated) BT601_625");
+
             case HAL_DATASPACE_BT601_525:
-            case HAL_DATASPACE_BT709:
-                return std::string("SMPTE_170M");
+                return std::string("(deprecated) BT601_525");
 
             case HAL_DATASPACE_SRGB_LINEAR:
-            case HAL_DATASPACE_ARBITRARY:
-                return std::string("Linear");
-
             case HAL_DATASPACE_SRGB:
-                return std::string("sRGB");
+                return std::string("(deprecated) sRGB");
+
+            case HAL_DATASPACE_BT709:
+                return std::string("(deprecated) BT709");
+
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("ARBITRARY");
 
             case HAL_DATASPACE_UNKNOWN:
             // Fallthrough
             default:
-                return std::string("");
+                return StringPrintf("Unknown deprecated dataspace code %d", dataspace);
         }
     }
+    return decodeStandardOnly(dataspaceStandard);
+}
 
-    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer) {
     switch (dataspaceTransfer) {
         case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
             return std::string("Unspecified");
@@ -151,6 +130,52 @@
     return StringPrintf("Unknown dataspace transfer %d", dataspaceTransfer);
 }
 
+std::string decodeTransfer(android_dataspace dataspace) {
+    const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
+    if (dataspaceSelect == 0) {
+        switch (dataspace & 0xffff) {
+            case HAL_DATASPACE_JFIF:
+            case HAL_DATASPACE_BT601_625:
+            case HAL_DATASPACE_BT601_525:
+            case HAL_DATASPACE_BT709:
+                return std::string("SMPTE_170M");
+
+            case HAL_DATASPACE_SRGB_LINEAR:
+            case HAL_DATASPACE_ARBITRARY:
+                return std::string("Linear");
+
+            case HAL_DATASPACE_SRGB:
+                return std::string("sRGB");
+
+            case HAL_DATASPACE_UNKNOWN:
+            // Fallthrough
+            default:
+                return std::string("");
+        }
+    }
+
+    const uint32_t dataspaceTransfer = (dataspace & HAL_DATASPACE_TRANSFER_MASK);
+    return decodeTransferOnly(dataspaceTransfer);
+}
+
+std::string decodeRangeOnly(uint32_t dataspaceRange) {
+    switch (dataspaceRange) {
+        case HAL_DATASPACE_RANGE_UNSPECIFIED:
+            return std::string("Range Unspecified");
+
+        case HAL_DATASPACE_RANGE_FULL:
+            return std::string("Full range");
+
+        case HAL_DATASPACE_RANGE_LIMITED:
+            return std::string("Limited range");
+
+        case HAL_DATASPACE_RANGE_EXTENDED:
+            return std::string("Extended range");
+    }
+
+    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+}
+
 std::string decodeRange(android_dataspace dataspace) {
     const uint32_t dataspaceSelect = (dataspace & HAL_DATASPACE_STANDARD_MASK);
     if (dataspaceSelect == 0) {
@@ -174,21 +199,7 @@
     }
 
     const uint32_t dataspaceRange = (dataspace & HAL_DATASPACE_RANGE_MASK);
-    switch (dataspaceRange) {
-        case HAL_DATASPACE_RANGE_UNSPECIFIED:
-            return std::string("Range Unspecified");
-
-        case HAL_DATASPACE_RANGE_FULL:
-            return std::string("Full range");
-
-        case HAL_DATASPACE_RANGE_LIMITED:
-            return std::string("Limited range");
-
-        case HAL_DATASPACE_RANGE_EXTENDED:
-            return std::string("Extended range");
-    }
-
-    return StringPrintf("Unknown dataspace range %d", dataspaceRange);
+    return decodeRangeOnly(dataspaceRange);
 }
 
 std::string dataspaceDetails(android_dataspace dataspace) {
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index ed7f193..8b13d78 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -23,67 +23,14 @@
 #include <optional>
 #include <span>
 
+#include <ftl/hash.h>
 #include <log/log.h>
-
 #include <ui/DisplayIdentification.h>
+#include <ui/Size.h>
 
 namespace android {
 namespace {
 
-template <class T>
-inline T load(const void* p) {
-    static_assert(std::is_integral<T>::value, "T must be integral");
-
-    T r;
-    std::memcpy(&r, p, sizeof(r));
-    return r;
-}
-
-uint64_t rotateByAtLeast1(uint64_t val, uint8_t shift) {
-    return (val >> shift) | (val << (64 - shift));
-}
-
-uint64_t shiftMix(uint64_t val) {
-    return val ^ (val >> 47);
-}
-
-__attribute__((no_sanitize("unsigned-integer-overflow")))
-uint64_t hash64Len16(uint64_t u, uint64_t v) {
-    constexpr uint64_t kMul = 0x9ddfea08eb382d69;
-    uint64_t a = (u ^ v) * kMul;
-    a ^= (a >> 47);
-    uint64_t b = (v ^ a) * kMul;
-    b ^= (b >> 47);
-    b *= kMul;
-    return b;
-}
-
-__attribute__((no_sanitize("unsigned-integer-overflow")))
-uint64_t hash64Len0To16(const char* s, uint64_t len) {
-    constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
-    constexpr uint64_t k3 = 0xc949d7c7509e6557;
-
-    if (len > 8) {
-        const uint64_t a = load<uint64_t>(s);
-        const uint64_t b = load<uint64_t>(s + len - 8);
-        return hash64Len16(a, rotateByAtLeast1(b + len, static_cast<uint8_t>(len))) ^ b;
-    }
-    if (len >= 4) {
-        const uint32_t a = load<uint32_t>(s);
-        const uint32_t b = load<uint32_t>(s + len - 4);
-        return hash64Len16(len + (a << 3), b);
-    }
-    if (len > 0) {
-        const unsigned char a = static_cast<unsigned char>(s[0]);
-        const unsigned char b = static_cast<unsigned char>(s[len >> 1]);
-        const unsigned char c = static_cast<unsigned char>(s[len - 1]);
-        const uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8);
-        const uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2);
-        return shiftMix(y * k2 ^ z * k3) * k2;
-    }
-    return k2;
-}
-
 using byte_view = std::span<const uint8_t>;
 
 constexpr size_t kEdidBlockSize = 128;
@@ -100,6 +47,10 @@
     return view[3];
 }
 
+bool isDetailedTimingDescriptor(const byte_view& view) {
+    return view[0] != 0 && view[1] != 0;
+}
+
 std::string_view parseEdidText(const byte_view& view) {
     std::string_view text(reinterpret_cast<const char*>(view.data()), view.size());
     text = text.substr(0, text.find('\n'));
@@ -273,6 +224,8 @@
     std::string_view displayName;
     std::string_view serialNumber;
     std::string_view asciiText;
+    ui::Size preferredDTDPixelSize;
+    ui::Size preferredDTDPhysicalSize;
 
     constexpr size_t kDescriptorCount = 4;
     constexpr size_t kDescriptorLength = 18;
@@ -297,6 +250,35 @@
                     serialNumber = parseEdidText(descriptor);
                     break;
             }
+        } else if (isDetailedTimingDescriptor(view)) {
+            static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
+            static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
+            static constexpr size_t kVerticalPhysicalLsbOffset = 13;
+            static constexpr size_t kVerticalPhysicalMsbOffset = 14;
+            const uint32_t hSize =
+                    static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
+                                          ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
+            const uint32_t vSize =
+                    static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
+                                          ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
+
+            static constexpr size_t kHorizontalPixelLsbOffset = 2;
+            static constexpr size_t kHorizontalPixelMsbOffset = 4;
+            static constexpr size_t kVerticalPixelLsbOffset = 5;
+            static constexpr size_t kVerticalPixelMsbOffset = 7;
+
+            const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
+            const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
+            const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
+
+            const uint8_t vLsb = view[kVerticalPixelLsbOffset];
+            const uint8_t vMsb = view[kVerticalPixelMsbOffset];
+            const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
+
+            preferredDTDPixelSize.setWidth(hPixel);
+            preferredDTDPixelSize.setHeight(vPixel);
+            preferredDTDPhysicalSize.setWidth(hSize);
+            preferredDTDPhysicalSize.setHeight(vSize);
         }
 
         view = view.subspan(kDescriptorLength);
@@ -320,7 +302,7 @@
     // Hash model string instead of using product code or (integer) serial number, since the latter
     // have been observed to change on some displays with multiple inputs. Use a stable hash instead
     // of std::hash which is only required to be same within a single execution of a program.
-    const uint32_t modelHash = static_cast<uint32_t>(cityHash64Len0To16(modelString));
+    const uint32_t modelHash = static_cast<uint32_t>(*ftl::stable_hash(modelString));
 
     // Parse extension blocks.
     std::optional<Cea861ExtensionBlock> cea861Block;
@@ -351,14 +333,22 @@
         }
     }
 
-    return Edid{.manufacturerId = manufacturerId,
-                .productId = productId,
-                .pnpId = *pnpId,
-                .modelHash = modelHash,
-                .displayName = displayName,
-                .manufactureOrModelYear = manufactureOrModelYear,
-                .manufactureWeek = manufactureWeek,
-                .cea861Block = cea861Block};
+    DetailedTimingDescriptor preferredDetailedTimingDescriptor{
+            .pixelSizeCount = preferredDTDPixelSize,
+            .physicalSizeInMm = preferredDTDPhysicalSize,
+    };
+
+    return Edid{
+            .manufacturerId = manufacturerId,
+            .productId = productId,
+            .pnpId = *pnpId,
+            .modelHash = modelHash,
+            .displayName = displayName,
+            .manufactureOrModelYear = manufactureOrModelYear,
+            .manufactureWeek = manufactureWeek,
+            .cea861Block = cea861Block,
+            .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
+    };
 }
 
 std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
@@ -390,22 +380,16 @@
     }
 
     const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
-    return DisplayIdentificationInfo{.id = displayId,
-                                     .name = std::string(edid->displayName),
-                                     .deviceProductInfo = buildDeviceProductInfo(*edid)};
+    return DisplayIdentificationInfo{
+            .id = displayId,
+            .name = std::string(edid->displayName),
+            .deviceProductInfo = buildDeviceProductInfo(*edid),
+            .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
+    };
 }
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
     return PhysicalDisplayId::fromEdid(0, kVirtualEdidManufacturerId, id);
 }
 
-uint64_t cityHash64Len0To16(std::string_view sv) {
-    auto len = sv.length();
-    if (len > 16) {
-        ALOGE("%s called with length %zu. Only hashing the first 16 chars", __FUNCTION__, len);
-        len = 16;
-    }
-    return hash64Len0To16(sv.data(), len);
-}
-
 } // namespace android
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
index c9ec036..2143f79 100644
--- a/libs/ui/Gralloc5.cpp
+++ b/libs/ui/Gralloc5.cpp
@@ -23,7 +23,6 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android/binder_manager.h>
 #include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
-#include <android/llndk-versioning.h>
 #include <binder/IPCThreadState.h>
 #include <dlfcn.h>
 #include <ui/FatVector.h>
@@ -91,7 +90,7 @@
         }
 
         void* so = nullptr;
-        if API_LEVEL_AT_LEAST (__ANDROID_API_V__, 202404) {
+        if (__builtin_available(android __ANDROID_API_V__, *)) {
             so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
                                                             RTLD_LOCAL | RTLD_NOW);
         } else {
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index ffb6cdb..18c9a6b 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -388,8 +388,8 @@
         }
     }
 
-    const uint64_t usage = static_cast<uint64_t>(
-            android_convertGralloc1To0Usage(inProducerUsage, inConsumerUsage));
+    const uint64_t usage = static_cast<uint64_t>(ANDROID_NATIVE_UNSIGNED_CAST(
+            android_convertGralloc1To0Usage(inProducerUsage, inConsumerUsage)));
 
     auto result = getBufferMapper().lock(handle, usage, rect, base::unique_fd{fenceFd});
 
@@ -596,6 +596,8 @@
             width = height = stride = format = usage_deprecated = 0;
             layerCount = 0;
             usage = 0;
+            native_handle_close(handle);
+            native_handle_delete(const_cast<native_handle_t*>(handle));
             handle = nullptr;
             ALOGE("unflatten: registerBuffer failed: %s (%d)", strerror(-err), err);
             return err;
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 98082fb..1ebe597 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -291,5 +291,9 @@
     return NO_ERROR;
 }
 
+bool GraphicBufferAllocator::supportsAdditionalOptions() const {
+    return mAllocator->supportsAdditionalOptions();
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index b6ab2f5..7b5a27d 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -208,8 +208,10 @@
 status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle, uint64_t producerUsage,
                                         uint64_t consumerUsage, const Rect& bounds, void** vaddr,
                                         int fenceFd) {
-    return lockAsync(handle, android_convertGralloc1To0Usage(producerUsage, consumerUsage), bounds,
-                     vaddr, fenceFd);
+    return lockAsync(handle,
+                     ANDROID_NATIVE_UNSIGNED_CAST(
+                             android_convertGralloc1To0Usage(producerUsage, consumerUsage)),
+                     bounds, vaddr, fenceFd);
 }
 
 status_t GraphicBufferMapper::lockAsyncYCbCr(buffer_handle_t handle, uint32_t usage,
diff --git a/libs/ui/OWNERS b/libs/ui/OWNERS
index a0b5fe7..2a85a4b 100644
--- a/libs/ui/OWNERS
+++ b/libs/ui/OWNERS
@@ -2,6 +2,5 @@
 alecmouri@google.com
 chrisforbes@google.com
 jreck@google.com
-lpy@google.com
 mathias@google.com
 romainguy@google.com
diff --git a/libs/ui/Transform.cpp b/libs/ui/Transform.cpp
index 42dd85e..23249fa 100644
--- a/libs/ui/Transform.cpp
+++ b/libs/ui/Transform.cpp
@@ -110,10 +110,12 @@
     return mMatrix[i];
 }
 
+// x translate
 float Transform::tx() const {
     return mMatrix[2][0];
 }
 
+// y translate
 float Transform::ty() const {
     return mMatrix[2][1];
 }
@@ -167,11 +169,15 @@
     }
 }
 
-void Transform::set(float a, float b, float c, float d) {
+// x and y are the coordinates in the destination (i.e. the screen)
+// s and t are the coordinates in the source (i.e. the texture)
+// d means derivative
+// dsdx means ds/dx derivative of s with respect to x, etc.
+void Transform::set(float dsdx, float dtdy, float dtdx, float dsdy) {
     mat33& M(mMatrix);
-    M[0][0] = a;    M[1][0] = b;
-    M[0][1] = c;    M[1][1] = d;
-    M[0][2] = 0;    M[1][2] = 0;
+    M[0][0] = dsdx;    M[1][0] = dtdy;
+    M[0][1] = dtdx;    M[1][1] = dsdy;
+    M[0][2] = 0;       M[1][2] = 0;
     mType = UNKNOWN_TYPE;
 }
 
diff --git a/libs/ui/include/ui/DebugUtils.h b/libs/ui/include/ui/DebugUtils.h
index 18cd487..7c4ac42 100644
--- a/libs/ui/include/ui/DebugUtils.h
+++ b/libs/ui/include/ui/DebugUtils.h
@@ -27,8 +27,11 @@
 }
 
 std::string decodeStandard(android_dataspace dataspace);
+std::string decodeStandardOnly(uint32_t dataspaceStandard);
 std::string decodeTransfer(android_dataspace dataspace);
+std::string decodeTransferOnly(uint32_t dataspaceTransfer);
 std::string decodeRange(android_dataspace dataspace);
+std::string decodeRangeOnly(uint32_t dataspaceRange);
 std::string dataspaceDetails(android_dataspace dataspace);
 std::string decodeColorMode(android::ui::ColorMode colormode);
 std::string decodeColorTransform(android_color_transform colorTransform);
diff --git a/libs/ui/include/ui/DisplayId.h b/libs/ui/include/ui/DisplayId.h
index 3a31fa0..8a14db8 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -20,6 +20,7 @@
 #include <ostream>
 #include <string>
 
+#include <ftl/hash.h>
 #include <ftl/optional.h>
 
 namespace android {
@@ -38,6 +39,9 @@
     constexpr DisplayId(const DisplayId&) = default;
     DisplayId& operator=(const DisplayId&) = default;
 
+    constexpr bool isVirtual() const { return value & FLAG_VIRTUAL; }
+    constexpr bool isStable() const { return value & FLAG_STABLE; }
+
     uint64_t value;
 
     // For deserialization.
@@ -76,10 +80,10 @@
 // DisplayId of a physical display, such as the internal display or externally connected display.
 struct PhysicalDisplayId : DisplayId {
     static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
-        if (id.value & FLAG_VIRTUAL) {
+        if (id.isVirtual()) {
             return std::nullopt;
         }
-        return {PhysicalDisplayId(id)};
+        return PhysicalDisplayId(id);
     }
 
     // Returns a stable ID based on EDID information.
@@ -117,25 +121,23 @@
     static constexpr uint64_t FLAG_GPU = 1ULL << 61;
 
     static constexpr std::optional<VirtualDisplayId> tryCast(DisplayId id) {
-        if (id.value & FLAG_VIRTUAL) {
-            return {VirtualDisplayId(id)};
+        if (id.isVirtual()) {
+            return VirtualDisplayId(id);
         }
         return std::nullopt;
     }
 
 protected:
-    constexpr VirtualDisplayId(uint64_t flags, BaseId baseId)
-          : DisplayId(DisplayId::FLAG_VIRTUAL | flags | baseId) {}
-
+    explicit constexpr VirtualDisplayId(uint64_t value) : DisplayId(FLAG_VIRTUAL | value) {}
     explicit constexpr VirtualDisplayId(DisplayId other) : DisplayId(other) {}
 };
 
 struct HalVirtualDisplayId : VirtualDisplayId {
-    explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(0, baseId) {}
+    explicit constexpr HalVirtualDisplayId(BaseId baseId) : VirtualDisplayId(baseId) {}
 
     static constexpr std::optional<HalVirtualDisplayId> tryCast(DisplayId id) {
-        if ((id.value & FLAG_VIRTUAL) && !(id.value & VirtualDisplayId::FLAG_GPU)) {
-            return {HalVirtualDisplayId(id)};
+        if (id.isVirtual() && !(id.value & FLAG_GPU)) {
+            return HalVirtualDisplayId(id);
         }
         return std::nullopt;
     }
@@ -145,17 +147,27 @@
 };
 
 struct GpuVirtualDisplayId : VirtualDisplayId {
-    explicit constexpr GpuVirtualDisplayId(BaseId baseId)
-          : VirtualDisplayId(VirtualDisplayId::FLAG_GPU, baseId) {}
+    explicit constexpr GpuVirtualDisplayId(BaseId baseId) : VirtualDisplayId(FLAG_GPU | baseId) {}
+
+    static constexpr std::optional<GpuVirtualDisplayId> fromUniqueId(const std::string& uniqueId) {
+        if (const auto hashOpt = ftl::stable_hash(uniqueId)) {
+            return GpuVirtualDisplayId(HashTag{}, *hashOpt);
+        }
+        return {};
+    }
 
     static constexpr std::optional<GpuVirtualDisplayId> tryCast(DisplayId id) {
-        if ((id.value & FLAG_VIRTUAL) && (id.value & VirtualDisplayId::FLAG_GPU)) {
-            return {GpuVirtualDisplayId(id)};
+        if (id.isVirtual() && (id.value & FLAG_GPU)) {
+            return GpuVirtualDisplayId(id);
         }
         return std::nullopt;
     }
 
 private:
+    struct HashTag {}; // Disambiguate with BaseId constructor.
+    constexpr GpuVirtualDisplayId(HashTag, uint64_t hash)
+          : VirtualDisplayId(FLAG_STABLE | FLAG_GPU | hash) {}
+
     explicit constexpr GpuVirtualDisplayId(DisplayId other) : VirtualDisplayId(other) {}
 };
 
@@ -169,7 +181,7 @@
         if (GpuVirtualDisplayId::tryCast(id)) {
             return std::nullopt;
         }
-        return {HalDisplayId(id)};
+        return HalDisplayId(id);
     }
 
 private:
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index fc9c0f4..648e024 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -25,6 +25,7 @@
 
 #include <ui/DeviceProductInfo.h>
 #include <ui/DisplayId.h>
+#include <ui/Size.h>
 
 #define LEGACY_DISPLAY_TYPE_PRIMARY 0
 #define LEGACY_DISPLAY_TYPE_EXTERNAL 1
@@ -33,10 +34,16 @@
 
 using DisplayIdentificationData = std::vector<uint8_t>;
 
+struct DetailedTimingDescriptor {
+    ui::Size pixelSizeCount;
+    ui::Size physicalSizeInMm;
+};
+
 struct DisplayIdentificationInfo {
     PhysicalDisplayId id;
     std::string name;
     std::optional<DeviceProductInfo> deviceProductInfo;
+    std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
 
 struct ExtensionBlock {
@@ -68,6 +75,7 @@
     uint8_t manufactureOrModelYear;
     uint8_t manufactureWeek;
     std::optional<Cea861ExtensionBlock> cea861Block;
+    std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
 
 bool isEdid(const DisplayIdentificationData&);
@@ -80,7 +88,4 @@
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id);
 
-// CityHash64 implementation that only hashes at most the first 16 characters of the given string.
-uint64_t cityHash64Len0To16(std::string_view sv);
-
 } // namespace android
diff --git a/libs/ui/include/ui/EdgeExtensionEffect.h b/libs/ui/include/ui/EdgeExtensionEffect.h
new file mode 100644
index 0000000..02126b1
--- /dev/null
+++ b/libs/ui/include/ui/EdgeExtensionEffect.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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
+
+/**
+ * Stores the edge that will be extended
+ */
+namespace android {
+
+enum CanonicalDirections { NONE = 0, LEFT = 1, RIGHT = 2, TOP = 4, BOTTOM = 8 };
+
+inline std::string to_string(CanonicalDirections direction) {
+    switch (direction) {
+        case LEFT:
+            return "LEFT";
+        case RIGHT:
+            return "RIGHT";
+        case TOP:
+            return "TOP";
+        case BOTTOM:
+            return "BOTTOM";
+        case NONE:
+            return "NONE";
+    }
+}
+
+struct EdgeExtensionEffect {
+    EdgeExtensionEffect(bool left, bool right, bool top, bool bottom) {
+        extensionEdges = NONE;
+        if (left) {
+            extensionEdges |= LEFT;
+        }
+        if (right) {
+            extensionEdges |= RIGHT;
+        }
+        if (top) {
+            extensionEdges |= TOP;
+        }
+        if (bottom) {
+            extensionEdges |= BOTTOM;
+        }
+    }
+
+    EdgeExtensionEffect() { EdgeExtensionEffect(false, false, false, false); }
+
+    bool extendsEdge(CanonicalDirections edge) const { return extensionEdges & edge; }
+
+    bool hasEffect() const { return extensionEdges != NONE; };
+
+    void reset() { extensionEdges = NONE; }
+
+    bool operator==(const EdgeExtensionEffect& other) const {
+        return extensionEdges == other.extensionEdges;
+    }
+
+    bool operator!=(const EdgeExtensionEffect& other) const { return !(*this == other); }
+
+private:
+    int extensionEdges;
+};
+
+inline std::string to_string(const EdgeExtensionEffect& effect) {
+    std::string s = "EdgeExtensionEffect={edges=[";
+    if (effect.hasEffect()) {
+        for (CanonicalDirections edge : {LEFT, RIGHT, TOP, BOTTOM}) {
+            if (effect.extendsEdge(edge)) {
+                s += to_string(edge) + ", ";
+            }
+        }
+    } else {
+        s += to_string(NONE);
+    }
+    s += "]}";
+    return s;
+}
+
+inline std::ostream& operator<<(std::ostream& os, const EdgeExtensionEffect effect) {
+    os << to_string(effect);
+    return os;
+}
+} // namespace android
diff --git a/libs/ui/include/ui/Fence.h b/libs/ui/include/ui/Fence.h
index 9aae145..a75ba37 100644
--- a/libs/ui/include/ui/Fence.h
+++ b/libs/ui/include/ui/Fence.h
@@ -156,6 +156,6 @@
     base::unique_fd mFenceFd;
 };
 
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_FENCE_H
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index e6015e0..4167dcb 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -226,6 +226,8 @@
             const GraphicBufferAllocator::AllocationRequest&) const {
         return GraphicBufferAllocator::AllocationResult(UNKNOWN_TRANSACTION);
     }
+
+    virtual bool supportsAdditionalOptions() const { return false; }
 };
 
 } // namespace android
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
index f9e8f5e..5aa5019 100644
--- a/libs/ui/include/ui/Gralloc5.h
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -178,6 +178,8 @@
     [[nodiscard]] GraphicBufferAllocator::AllocationResult allocate(
             const GraphicBufferAllocator::AllocationRequest&) const override;
 
+    bool supportsAdditionalOptions() const override { return true; }
+
 private:
     const Gralloc5Mapper &mMapper;
     std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index 652d8ba..936bf8f 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -297,6 +297,6 @@
             mDeathCallbacks;
 };
 
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_GRAPHIC_BUFFER_H
diff --git a/libs/ui/include/ui/GraphicBufferAllocator.h b/libs/ui/include/ui/GraphicBufferAllocator.h
index 8f461e1..97ed05a 100644
--- a/libs/ui/include/ui/GraphicBufferAllocator.h
+++ b/libs/ui/include/ui/GraphicBufferAllocator.h
@@ -107,6 +107,8 @@
     void dump(std::string& res, bool less = true) const;
     static void dumpToSystemLog(bool less = true);
 
+    bool supportsAdditionalOptions() const;
+
 protected:
     struct alloc_rec_t {
         uint32_t width;
@@ -135,6 +137,6 @@
 };
 
 // ---------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_BUFFER_ALLOCATOR_H
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 9da1447..91aabe9 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -188,7 +188,7 @@
 
 // ---------------------------------------------------------------------------
 
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_UI_BUFFER_MAPPER_H
 
diff --git a/libs/ui/include/ui/LayerStack.h b/libs/ui/include/ui/LayerStack.h
index d6ffeb7..f4c8ba2 100644
--- a/libs/ui/include/ui/LayerStack.h
+++ b/libs/ui/include/ui/LayerStack.h
@@ -55,6 +55,10 @@
     return lhs.id > rhs.id;
 }
 
+inline bool operator<(LayerStack lhs, LayerStack rhs) {
+    return lhs.id < rhs.id;
+}
+
 // A LayerFilter determines if a layer is included for output to a display.
 struct LayerFilter {
     LayerStack layerStack;
diff --git a/libs/ui/include/ui/LogicalDisplayId.h b/libs/ui/include/ui/LogicalDisplayId.h
new file mode 100644
index 0000000..fd84b12
--- /dev/null
+++ b/libs/ui/include/ui/LogicalDisplayId.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/mixins.h>
+#include <sys/types.h>
+#include <string>
+
+#include <ostream>
+
+namespace android::ui {
+
+// Type-safe wrapper for a logical display id.
+struct LogicalDisplayId : ftl::Constructible<LogicalDisplayId, int32_t>,
+                          ftl::Equatable<LogicalDisplayId>,
+                          ftl::Orderable<LogicalDisplayId> {
+    using Constructible::Constructible;
+
+    constexpr auto val() const { return ftl::to_underlying(*this); }
+
+    constexpr bool isValid() const { return val() >= 0; }
+
+    std::string toString() const { return std::to_string(val()); }
+
+    static const LogicalDisplayId INVALID;
+    static const LogicalDisplayId DEFAULT;
+};
+
+constexpr inline LogicalDisplayId LogicalDisplayId::INVALID{-1};
+constexpr inline LogicalDisplayId LogicalDisplayId::DEFAULT{0};
+
+inline std::ostream& operator<<(std::ostream& stream, LogicalDisplayId displayId) {
+    return stream << displayId.val();
+}
+
+} // namespace android::ui
+
+namespace std {
+template <>
+struct hash<android::ui::LogicalDisplayId> {
+    size_t operator()(const android::ui::LogicalDisplayId& displayId) const {
+        return hash<int32_t>()(displayId.val());
+    }
+};
+} // namespace std
\ No newline at end of file
diff --git a/libs/ui/include/ui/PixelFormat.h b/libs/ui/include/ui/PixelFormat.h
index cf5c2e8..1f20787 100644
--- a/libs/ui/include/ui/PixelFormat.h
+++ b/libs/ui/include/ui/PixelFormat.h
@@ -72,6 +72,6 @@
 
 uint32_t bytesPerPixel(PixelFormat format);
 
-}; // namespace android
+} // namespace android
 
 #endif // UI_PIXELFORMAT_H
diff --git a/libs/ui/include/ui/Point.h b/libs/ui/include/ui/Point.h
index d050ede..97a54be 100644
--- a/libs/ui/include/ui/Point.h
+++ b/libs/ui/include/ui/Point.h
@@ -83,6 +83,6 @@
 
 ANDROID_BASIC_TYPES_TRAITS(Point)
 
-}; // namespace android
+} // namespace android
 
 #endif // ANDROID_UI_POINT
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index 2248cca..7b7f502 100644
--- a/libs/ui/include/ui/PublicFormat.h
+++ b/libs/ui/include/ui/PublicFormat.h
@@ -59,6 +59,7 @@
     DEPTH_JPEG = 0x69656963,
     JPEG_R = 0x1005,
     HEIC = 0x48454946,
+    YCBCR_P210 = 0x3c,
 };
 
 /* Convert from android.graphics.ImageFormat/PixelFormat enums to graphics.h HAL
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 9e24a07..2eb9330 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -233,7 +233,7 @@
 
 ANDROID_BASIC_TYPES_TRAITS(Rect)
 
-}; // namespace android
+} // namespace android
 
 namespace std {
 template <>
diff --git a/libs/ui/include/ui/Region.h b/libs/ui/include/ui/Region.h
index 927c334..d1b38f3 100644
--- a/libs/ui/include/ui/Region.h
+++ b/libs/ui/include/ui/Region.h
@@ -233,7 +233,7 @@
 }
 
 // ---------------------------------------------------------------------------
-}; // namespace android
+} // namespace android
 
 namespace std {
 template <>
diff --git a/libs/ui/tests/DisplayId_test.cpp b/libs/ui/tests/DisplayId_test.cpp
index 8ddee7e..ef686df 100644
--- a/libs/ui/tests/DisplayId_test.cpp
+++ b/libs/ui/tests/DisplayId_test.cpp
@@ -24,7 +24,7 @@
     constexpr uint8_t port = 1;
     constexpr uint16_t manufacturerId = 13;
     constexpr uint32_t modelHash = 42;
-    PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash);
+    const PhysicalDisplayId id = PhysicalDisplayId::fromEdid(port, manufacturerId, modelHash);
     EXPECT_EQ(port, id.getPort());
     EXPECT_EQ(manufacturerId, id.getManufacturerId());
     EXPECT_FALSE(VirtualDisplayId::tryCast(id));
@@ -39,7 +39,7 @@
 
 TEST(DisplayIdTest, createPhysicalIdFromPort) {
     constexpr uint8_t port = 3;
-    PhysicalDisplayId id = PhysicalDisplayId::fromPort(port);
+    const PhysicalDisplayId id = PhysicalDisplayId::fromPort(port);
     EXPECT_EQ(port, id.getPort());
     EXPECT_FALSE(VirtualDisplayId::tryCast(id));
     EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
@@ -52,7 +52,34 @@
 }
 
 TEST(DisplayIdTest, createGpuVirtualId) {
-    GpuVirtualDisplayId id(42);
+    const GpuVirtualDisplayId id(42);
+    EXPECT_TRUE(VirtualDisplayId::tryCast(id));
+    EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
+    EXPECT_FALSE(HalDisplayId::tryCast(id));
+
+    EXPECT_EQ(id, DisplayId::fromValue(id.value));
+    EXPECT_EQ(id, DisplayId::fromValue<GpuVirtualDisplayId>(id.value));
+}
+
+TEST(DisplayIdTest, createVirtualIdFromGpuVirtualId) {
+    const VirtualDisplayId id(GpuVirtualDisplayId(42));
+    EXPECT_TRUE(VirtualDisplayId::tryCast(id));
+    EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
+    EXPECT_FALSE(HalDisplayId::tryCast(id));
+
+    const bool isGpuVirtualId = (id.value & VirtualDisplayId::FLAG_GPU);
+    EXPECT_EQ((id.isVirtual() && isGpuVirtualId), GpuVirtualDisplayId::tryCast(id).has_value());
+}
+
+TEST(DisplayIdTest, createGpuVirtualIdFromUniqueId) {
+    static const std::string kUniqueId("virtual:ui:DisplayId_test");
+    const auto idOpt = GpuVirtualDisplayId::fromUniqueId(kUniqueId);
+    ASSERT_TRUE(idOpt.has_value());
+    const GpuVirtualDisplayId id = idOpt.value();
     EXPECT_TRUE(VirtualDisplayId::tryCast(id));
     EXPECT_TRUE(GpuVirtualDisplayId::tryCast(id));
     EXPECT_FALSE(HalVirtualDisplayId::tryCast(id));
@@ -64,7 +91,7 @@
 }
 
 TEST(DisplayIdTest, createHalVirtualId) {
-    HalVirtualDisplayId id(42);
+    const HalVirtualDisplayId id(42);
     EXPECT_TRUE(VirtualDisplayId::tryCast(id));
     EXPECT_TRUE(HalVirtualDisplayId::tryCast(id));
     EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
@@ -75,4 +102,16 @@
     EXPECT_EQ(id, DisplayId::fromValue<HalVirtualDisplayId>(id.value));
 }
 
+TEST(DisplayIdTest, createVirtualIdFromHalVirtualId) {
+    const VirtualDisplayId id(HalVirtualDisplayId(42));
+    EXPECT_TRUE(VirtualDisplayId::tryCast(id));
+    EXPECT_TRUE(HalVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(GpuVirtualDisplayId::tryCast(id));
+    EXPECT_FALSE(PhysicalDisplayId::tryCast(id));
+    EXPECT_TRUE(HalDisplayId::tryCast(id));
+
+    const bool isGpuVirtualId = (id.value & VirtualDisplayId::FLAG_GPU);
+    EXPECT_EQ((id.isVirtual() && !isGpuVirtualId), HalVirtualDisplayId::tryCast(id).has_value());
+}
+
 } // namespace android::ui
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 736979a..76e3f66 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -21,9 +21,9 @@
 #include <functional>
 #include <string_view>
 
+#include <ftl/hash.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
 #include <ui/DisplayIdentification.h>
 
 using ::testing::ElementsAre;
@@ -135,7 +135,7 @@
 }
 
 uint32_t hash(const char* str) {
-    return static_cast<uint32_t>(cityHash64Len0To16(str));
+    return static_cast<uint32_t>(*ftl::stable_hash(str));
 }
 
 } // namespace
@@ -188,28 +188,39 @@
     EXPECT_STREQ("SEC", edid->pnpId.data());
     // ASCII text should be used as fallback if display name and serial number are missing.
     EXPECT_EQ(hash("121AT11-801"), edid->modelHash);
+    EXPECT_EQ(hash("121AT11-801"), 626564263);
     EXPECT_TRUE(edid->displayName.empty());
     EXPECT_EQ(12610, edid->productId);
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
     EXPECT_FALSE(edid->cea861Block);
+    EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(261, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(163, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getExternalEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x22f0u, edid->manufacturerId);
     EXPECT_STREQ("HWP", edid->pnpId.data());
     EXPECT_EQ(hash("HP ZR30w"), edid->modelHash);
+    EXPECT_EQ(hash("HP ZR30w"), 918492362);
     EXPECT_EQ("HP ZR30w", edid->displayName);
     EXPECT_EQ(10348, edid->productId);
     EXPECT_EQ(22, edid->manufactureOrModelYear);
     EXPECT_EQ(2, edid->manufactureWeek);
     EXPECT_FALSE(edid->cea861Block);
+    EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(641, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(400, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getExternalEedid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(0x4c2du, edid->manufacturerId);
     EXPECT_STREQ("SAM", edid->pnpId.data());
     EXPECT_EQ(hash("SAMSUNG"), edid->modelHash);
+    EXPECT_EQ(hash("SAMSUNG"), 1201368132);
     EXPECT_EQ("SAMSUNG", edid->displayName);
     EXPECT_EQ(2302, edid->productId);
     EXPECT_EQ(21, edid->manufactureOrModelYear);
@@ -221,12 +232,17 @@
     EXPECT_EQ(0, physicalAddress.b);
     EXPECT_EQ(0, physicalAddress.c);
     EXPECT_EQ(0, physicalAddress.d);
+    EXPECT_EQ(1366, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(768, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(160, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(90, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getPanasonicTvEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(13481, edid->manufacturerId);
     EXPECT_STREQ("MEI", edid->pnpId.data());
     EXPECT_EQ(hash("Panasonic-TV"), edid->modelHash);
+    EXPECT_EQ(hash("Panasonic-TV"), 3876373262);
     EXPECT_EQ("Panasonic-TV", edid->displayName);
     EXPECT_EQ(41622, edid->productId);
     EXPECT_EQ(29, edid->manufactureOrModelYear);
@@ -238,12 +254,17 @@
     EXPECT_EQ(0, physicalAddress.b);
     EXPECT_EQ(0, physicalAddress.c);
     EXPECT_EQ(0, physicalAddress.d);
+    EXPECT_EQ(1920, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(1080, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(698, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(392, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getHisenseTvEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(8355, edid->manufacturerId);
     EXPECT_STREQ("HEC", edid->pnpId.data());
     EXPECT_EQ(hash("Hisense"), edid->modelHash);
+    EXPECT_EQ(hash("Hisense"), 2859844809);
     EXPECT_EQ("Hisense", edid->displayName);
     EXPECT_EQ(0, edid->productId);
     EXPECT_EQ(29, edid->manufactureOrModelYear);
@@ -255,18 +276,27 @@
     EXPECT_EQ(2, physicalAddress.b);
     EXPECT_EQ(3, physicalAddress.c);
     EXPECT_EQ(4, physicalAddress.d);
+    EXPECT_EQ(1920, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(1080, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(575, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(323, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getCtlDisplayEdid());
     ASSERT_TRUE(edid);
     EXPECT_EQ(3724, edid->manufacturerId);
     EXPECT_STREQ("CTL", edid->pnpId.data());
     EXPECT_EQ(hash("LP2361"), edid->modelHash);
+    EXPECT_EQ(hash("LP2361"), 1523181158);
     EXPECT_EQ("LP2361", edid->displayName);
     EXPECT_EQ(9373, edid->productId);
     EXPECT_EQ(23, edid->manufactureOrModelYear);
     EXPECT_EQ(0xff, edid->manufactureWeek);
     ASSERT_TRUE(edid->cea861Block);
     EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock);
+    EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(768, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(521, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(293, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 }
 
 TEST(DisplayIdentificationTest, parseInvalidEdid) {
@@ -281,6 +311,7 @@
     // Serial number should be used as fallback if display name is invalid.
     const auto modelHash = hash("CN4202137Q");
     EXPECT_EQ(modelHash, edid->modelHash);
+    EXPECT_EQ(modelHash, 3582951527);
     EXPECT_TRUE(edid->displayName.empty());
 
     // Parsing should succeed even if EDID is truncated.
diff --git a/libs/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp
deleted file mode 100644
index eda5ea4..0000000
--- a/libs/ultrahdr/Android.bp
+++ /dev/null
@@ -1,85 +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.
-
-package {
-    // See: http://go/android-license-faq
-    default_applicable_licenses: [
-        "frameworks_native_license",
-        "adobe_hdr_gain_map_license",
-    ],
-}
-
-cc_library {
-    name: "libultrahdr-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-    export_include_dirs: ["include"],
-    local_include_dirs: ["include"],
-
-    srcs: [
-        "icc.cpp",
-        "jpegr.cpp",
-        "gainmapmath.cpp",
-        "jpegrutils.cpp",
-        "multipictureformat.cpp",
-    ],
-
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-        "libjpegencoder",
-        "libjpegdecoder",
-        "liblog",
-        "libutils",
-    ],
-}
-
-cc_library {
-    name: "libjpegencoder-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-        "libutils",
-    ],
-
-    export_include_dirs: ["include"],
-
-    srcs: [
-        "jpegencoderhelper.cpp",
-    ],
-}
-
-cc_library {
-    name: "libjpegdecoder-deprecated",
-    enabled: false,
-    host_supported: true,
-    vendor_available: true,
-
-    shared_libs: [
-        "libjpeg",
-        "liblog",
-        "libutils",
-    ],
-
-    export_include_dirs: ["include"],
-
-    srcs: [
-        "jpegdecoderhelper.cpp",
-    ],
-}
diff --git a/libs/ultrahdr/OWNERS b/libs/ultrahdr/OWNERS
deleted file mode 100644
index 6ace354..0000000
--- a/libs/ultrahdr/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-arifdikici@google.com
-dichenzhang@google.com
-kyslov@google.com
\ No newline at end of file
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp b/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
deleted file mode 100644
index 2fa361f..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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.
-
-license {
-    name: "adobe_hdr_gain_map_license-deprecated",
-    license_kinds: ["legacy_by_exception_only"],
-    license_text: ["NOTICE"],
-}
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
deleted file mode 100644
index 3f6c594..0000000
--- a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
+++ /dev/null
@@ -1 +0,0 @@
-This product includes Gain Map technology under license by Adobe.
diff --git a/libs/ultrahdr/fuzzer/Android.bp b/libs/ultrahdr/fuzzer/Android.bp
deleted file mode 100644
index 8d9132f..0000000
--- a/libs/ultrahdr/fuzzer/Android.bp
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_defaults {
-    name: "ultrahdr_fuzzer_defaults-deprecated",
-    enabled: false,
-    host_supported: true,
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-    ],
-    static_libs: [
-        "libjpegdecoder",
-        "libjpegencoder",
-        "libultrahdr",
-        "libutils",
-        "liblog",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-    fuzz_config: {
-        cc: [
-            "android-media-fuzzing-reports@google.com",
-        ],
-        description: "The fuzzers target the APIs of jpeg hdr",
-        service_privilege: "constrained",
-        users: "multi_user",
-        fuzzed_code_usage: "future_version",
-        vector: "local_no_privileges_required",
-    },
-}
-
-cc_fuzz {
-    name: "ultrahdr_enc_fuzzer-deprecated",
-    enabled: false,
-    defaults: ["ultrahdr_fuzzer_defaults"],
-    srcs: [
-        "ultrahdr_enc_fuzzer.cpp",
-    ],
-}
-
-cc_fuzz {
-    name: "ultrahdr_dec_fuzzer-deprecated",
-    enabled: false,
-    defaults: ["ultrahdr_fuzzer_defaults"],
-    srcs: [
-        "ultrahdr_dec_fuzzer.cpp",
-    ],
-}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
deleted file mode 100644
index f1f4035..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <iostream>
-#include <vector>
-
-// User include files
-#include "ultrahdr/jpegr.h"
-
-using namespace android::ultrahdr;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-class UltraHdrDecFuzzer {
-public:
-    UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrDecFuzzer::process() {
-    // hdr_of
-    auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-    auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>();
-    jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(),
-                                     ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-    std::vector<uint8_t> iccData(0);
-    std::vector<uint8_t> exifData(0);
-    jpegr_info_struct info{0, 0, &iccData, &exifData};
-    JpegR jpegHdr;
-    (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-    std::cout << "input buffer size " << jpegImgR.length << std::endl;
-    std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
-#endif
-    size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-    jpegr_uncompressed_struct decodedJpegR;
-    auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-    decodedJpegR.data = decodedRaw.get();
-    ultrahdr_metadata_struct metadata;
-    jpegr_uncompressed_struct decodedGainMap{};
-    (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                              mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
-                              &decodedGainMap, &metadata);
-    if (decodedGainMap.data) free(decodedGainMap.data);
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrDecFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
deleted file mode 100644
index 2d59e8b..0000000
--- a/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * 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.
- */
-
-// System include files
-#include <fuzzer/FuzzedDataProvider.h>
-#include <algorithm>
-#include <iostream>
-#include <random>
-#include <vector>
-
-// User include files
-#include "ultrahdr/gainmapmath.h"
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "utils/Log.h"
-
-using namespace android::ultrahdr;
-
-// Color gamuts for image data, sync with ultrahdr.h
-const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
-const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1;
-const int kTfMax = ULTRAHDR_TF_PQ;
-
-// Transfer functions for image data, sync with ultrahdr.h
-const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
-const int kOfMax = ULTRAHDR_OUTPUT_MAX;
-
-// quality factor
-const int kQfMin = 0;
-const int kQfMax = 100;
-
-class UltraHdrEncFuzzer {
-public:
-    UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
-    void process();
-    void fillP010Buffer(uint16_t* data, int width, int height, int stride);
-    void fill420Buffer(uint8_t* data, int width, int height, int stride);
-
-private:
-    FuzzedDataProvider mFdp;
-};
-
-void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
-    uint16_t* tmp = data;
-    std::vector<uint16_t> buffer(16);
-    for (int i = 0; i < buffer.size(); i++) {
-        buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
-    }
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
-    uint8_t* tmp = data;
-    std::vector<uint8_t> buffer(16);
-    mFdp.ConsumeData(buffer.data(), buffer.size());
-    for (int j = 0; j < height; j++) {
-        for (int i = 0; i < width; i += buffer.size()) {
-            memcpy(tmp + i, buffer.data(),
-                   std::min((int)buffer.size(), (width - i)) * sizeof(*data));
-            std::shuffle(buffer.begin(), buffer.end(),
-                         std::default_random_engine(std::random_device{}()));
-        }
-        tmp += stride;
-    }
-}
-
-void UltraHdrEncFuzzer::process() {
-    while (mFdp.remaining_bytes()) {
-        struct jpegr_uncompressed_struct p010Img {};
-        struct jpegr_uncompressed_struct yuv420Img {};
-        struct jpegr_uncompressed_struct grayImg {};
-        struct jpegr_compressed_struct jpegImgR {};
-        struct jpegr_compressed_struct jpegImg {};
-        struct jpegr_compressed_struct jpegGainMap {};
-
-        // which encode api to select
-        int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4);
-
-        // quality factor
-        int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax);
-
-        // hdr_tf
-        auto tf = static_cast<ultrahdr_transfer_function>(
-                mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax));
-
-        // p010 Cg
-        auto p010Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // 420 Cg
-        auto yuv420Cg =
-                static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
-
-        // hdr_of
-        auto of = static_cast<ultrahdr_output_format>(
-                mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
-
-        int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth);
-        width = (width >> 1) << 1;
-
-        int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
-        height = (height >> 1) << 1;
-
-        std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
-        std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
-        std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
-        std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
-        if (muxSwitch != 4) {
-            // init p010 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            p010Img.width = width;
-            p010Img.height = height;
-            p010Img.colorGamut = p010Cg;
-            p010Img.luma_stride = hasYStride ? yStride : 0;
-            int bppP010 = 2;
-            if (isUVContiguous) {
-                size_t p010Size = yStride * height * 3 / 2;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
-                p010Img.data = bufferYHdr.get();
-                p010Img.chroma_data = nullptr;
-                p010Img.chroma_stride = 0;
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
-                size_t p010YSize = yStride * height;
-                bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
-                p010Img.data = bufferYHdr.get();
-                fillP010Buffer(bufferYHdr.get(), width, height, yStride);
-                size_t p010UVSize = uvStride * p010Img.height / 2;
-                bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
-                p010Img.chroma_data = bufferUVHdr.get();
-                p010Img.chroma_stride = uvStride;
-                fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
-            }
-        } else {
-            size_t map_width = width / kMapDimensionScaleFactor;
-            size_t map_height = height / kMapDimensionScaleFactor;
-            // init 400 image
-            grayImg.width = map_width;
-            grayImg.height = map_height;
-            grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-
-            const size_t graySize = map_width * map_height;
-            grayImgRaw = std::make_unique<uint8_t[]>(graySize);
-            grayImg.data = grayImgRaw.get();
-            fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
-            grayImg.chroma_data = nullptr;
-            grayImg.luma_stride = 0;
-            grayImg.chroma_stride = 0;
-        }
-
-        if (muxSwitch > 0) {
-            // init 420 image
-            bool isUVContiguous = mFdp.ConsumeBool();
-            bool hasYStride = mFdp.ConsumeBool();
-            int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
-            yuv420Img.width = width;
-            yuv420Img.height = height;
-            yuv420Img.colorGamut = yuv420Cg;
-            yuv420Img.luma_stride = hasYStride ? yStride : 0;
-            if (isUVContiguous) {
-                size_t yuv420Size = yStride * height * 3 / 2;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
-                yuv420Img.data = bufferYSdr.get();
-                yuv420Img.chroma_data = nullptr;
-                yuv420Img.chroma_stride = 0;
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
-                              yStride / 2);
-                fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
-                              yStride / 2);
-            } else {
-                int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
-                size_t yuv420YSize = yStride * height;
-                bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
-                yuv420Img.data = bufferYSdr.get();
-                fill420Buffer(bufferYSdr.get(), width, height, yStride);
-                size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
-                bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
-                yuv420Img.chroma_data = bufferUVSdr.get();
-                yuv420Img.chroma_stride = uvStride;
-                fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
-                fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
-                              uvStride);
-            }
-        }
-
-        // dest
-        // 2 * p010 size as input data is random, DCT compression might not behave as expected
-        jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2);
-        auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength);
-        jpegImgR.data = jpegImgRaw.get();
-
-//#define DUMP_PARAM
-#ifdef DUMP_PARAM
-        std::cout << "Api Select " << muxSwitch << std::endl;
-        std::cout << "image dimensions " << width << " x " << height << std::endl;
-        std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl;
-        std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
-        std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
-        std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
-        std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
-        std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
-        std::cout << "quality factor " << quality << std::endl;
-#endif
-
-        JpegR jpegHdr;
-        android::status_t status = android::UNKNOWN_ERROR;
-        if (muxSwitch == 0) { // api 0
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr);
-        } else if (muxSwitch == 1) { // api 1
-            jpegImgR.length = 0;
-            status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr);
-        } else {
-            // compressed img
-            JpegEncoderHelper encoder;
-            struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
-            if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
-            if (!yuv420ImgCopy.chroma_data) {
-                uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
-                yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
-                yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
-            }
-
-            if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
-                                      reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
-                                      yuv420ImgCopy.width, yuv420ImgCopy.height,
-                                      yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
-                                      quality, nullptr, 0)) {
-                jpegImg.length = encoder.getCompressedImageSize();
-                jpegImg.maxLength = jpegImg.length;
-                jpegImg.data = encoder.getCompressedImagePtr();
-                jpegImg.colorGamut = yuv420Cg;
-
-                if (muxSwitch == 2) { // api 2
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 3) { // api 3
-                    jpegImgR.length = 0;
-                    status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR);
-                } else if (muxSwitch == 4) { // api 4
-                    jpegImgR.length = 0;
-                    JpegEncoderHelper gainMapEncoder;
-                    if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
-                                                     nullptr, grayImg.width, grayImg.height,
-                                                     grayImg.width, 0, quality, nullptr, 0)) {
-                        jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
-                        jpegGainMap.maxLength = jpegImg.length;
-                        jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
-                        jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-                        ultrahdr_metadata_struct metadata;
-                        metadata.version = kJpegrVersion;
-                        if (tf == ULTRAHDR_TF_HLG) {
-                            metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
-                        } else if (tf == ULTRAHDR_TF_PQ) {
-                            metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
-                        } else {
-                            metadata.maxContentBoost = 1.0f;
-                        }
-                        metadata.minContentBoost = 1.0f;
-                        metadata.gamma = 1.0f;
-                        metadata.offsetSdr = 0.0f;
-                        metadata.offsetHdr = 0.0f;
-                        metadata.hdrCapacityMin = 1.0f;
-                        metadata.hdrCapacityMax = metadata.maxContentBoost;
-                        status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
-                    }
-                }
-            }
-        }
-        if (status == android::OK) {
-            std::vector<uint8_t> iccData(0);
-            std::vector<uint8_t> exifData(0);
-            jpegr_info_struct info{0, 0, &iccData, &exifData};
-            status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
-            if (status == android::OK) {
-                size_t outSize =
-                        info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
-                jpegr_uncompressed_struct decodedJpegR;
-                auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
-                decodedJpegR.data = decodedRaw.get();
-                ultrahdr_metadata_struct metadata;
-                jpegr_uncompressed_struct decodedGainMap{};
-                status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
-                                             mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX),
-                                             nullptr, of, &decodedGainMap, &metadata);
-                if (status != android::OK) {
-                    ALOGE("encountered error during decoding %d", status);
-                }
-                if (decodedGainMap.data) free(decodedGainMap.data);
-            } else {
-                ALOGE("encountered error during get jpeg info %d", status);
-            }
-        } else {
-            ALOGE("encountered error during encoding %d", status);
-        }
-    }
-}
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    UltraHdrEncFuzzer fuzzHandle(data, size);
-    fuzzHandle.process();
-    return 0;
-}
diff --git a/libs/ultrahdr/gainmapmath.cpp b/libs/ultrahdr/gainmapmath.cpp
deleted file mode 100644
index ae9c4ca..0000000
--- a/libs/ultrahdr/gainmapmath.cpp
+++ /dev/null
@@ -1,775 +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 <cmath>
-#include <vector>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-static const std::vector<float> kPqOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
-      result.push_back(pqOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kPqInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
-      result.push_back(pqInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
-      result.push_back(hlgOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kHlgInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
-      result.push_back(hlgInvOetf(value));
-    }
-    return result;
-}();
-
-static const std::vector<float> kSrgbInvOETF = [] {
-    std::vector<float> result;
-    for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
-      float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1);
-      result.push_back(srgbInvOetf(value));
-    }
-    return result;
-}();
-
-// Use Shepard's method for inverse distance weighting. For more information:
-// en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) {
-  return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1));
-}
-
-void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) {
-  for (int y = 0; y < mMapScaleFactor; y++) {
-    for (int x = 0; x < mMapScaleFactor; x++) {
-      float pos_x = ((float)x) / mMapScaleFactor;
-      float pos_y = ((float)y) / mMapScaleFactor;
-      int curr_x = floor(pos_x);
-      int curr_y = floor(pos_y);
-      int next_x = curr_x + incR;
-      int next_y = curr_y + incB;
-      float e1_distance = euclideanDistance(pos_x, curr_x, pos_y, curr_y);
-      int index = y * mMapScaleFactor * 4 + x * 4;
-      if (e1_distance == 0) {
-        weights[index++] = 1.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-        weights[index++] = 0.f;
-      } else {
-        float e1_weight = 1.f / e1_distance;
-
-        float e2_distance = euclideanDistance(pos_x, curr_x, pos_y, next_y);
-        float e2_weight = 1.f / e2_distance;
-
-        float e3_distance = euclideanDistance(pos_x, next_x, pos_y, curr_y);
-        float e3_weight = 1.f / e3_distance;
-
-        float e4_distance = euclideanDistance(pos_x, next_x, pos_y, next_y);
-        float e4_weight = 1.f / e4_distance;
-
-        float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-        weights[index++] = e1_weight / total_weight;
-        weights[index++] = e2_weight / total_weight;
-        weights[index++] = e3_weight / total_weight;
-        weights[index++] = e4_weight / total_weight;
-      }
-    }
-  }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-
-static const float kMaxPixelFloat = 1.0f;
-static float clampPixelFloat(float value) {
-    return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value;
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equation F.7.
-static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f;
-
-float srgbLuminance(Color e) {
-  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Uses the same coefficients for deriving luma signal as
-// IEC 61966-2-1/Amd 1:2003 states for luminance, so we reuse the luminance
-// function above.
-static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f;
-
-Color srgbRgbToYuv(Color e_gamma) {
-  float y_gamma = srgbLuminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kSrgbCb,
-             (e_gamma.r - y_gamma) / kSrgbCr }}};
-}
-
-// See ITU-R BT.709-6, Section 3.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to srgbRgbToYuv, we
-// can reuse the luminance coefficients since they are the same.
-static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG;
-static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG;
-
-Color srgbYuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}};
-}
-
-// See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6.
-float srgbInvOetf(float e_gamma) {
-  if (e_gamma <= 0.04045f) {
-    return e_gamma / 12.92f;
-  } else {
-    return pow((e_gamma + 0.055f) / 1.055f, 2.4);
-  }
-}
-
-Color srgbInvOetf(Color e_gamma) {
-  return {{{ srgbInvOetf(e_gamma.r),
-             srgbInvOetf(e_gamma.g),
-             srgbInvOetf(e_gamma.b) }}};
-}
-
-// See IEC 61966-2-1, Equations F.5 and F.6.
-float srgbInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
-  return kSrgbInvOETF[value];
-}
-
-Color srgbInvOetfLUT(Color e_gamma) {
-  return {{{ srgbInvOetfLUT(e_gamma.r),
-             srgbInvOetfLUT(e_gamma.g),
-             srgbInvOetfLUT(e_gamma.b) }}};
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-// See SMPTE EG 432-1, Equation 7-8.
-static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
-
-float p3Luminance(Color e) {
-  return kP3R * e.r + kP3G * e.g + kP3B * e.b;
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Unfortunately, calculation of luma signal differs from calculation of
-// luminance for Display-P3, so we can't reuse p3Luminance here.
-static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
-static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
-
-Color p3RgbToYuv(Color e_gamma) {
-  float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kP3Cb,
-             (e_gamma.r - y_gamma) / kP3Cr }}};
-}
-
-// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
-// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
-// use luma signal coefficients rather than the luminance coefficients.
-static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
-static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
-
-Color p3YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
-static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
-
-float bt2100Luminance(Color e) {
-  return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-// BT.2100 uses the same coefficients for calculating luma signal and luminance,
-// so we reuse the luminance function here.
-static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
-
-Color bt2100RgbToYuv(Color e_gamma) {
-  float y_gamma = bt2100Luminance(e_gamma);
-  return {{{ y_gamma,
-             (e_gamma.b - y_gamma) / kBt2100Cb,
-             (e_gamma.r - y_gamma) / kBt2100Cr }}};
-}
-
-// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
-//
-// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
-//
-// Derived by inversing bt2100RgbToYuv. The derivation for R and B are  pretty
-// straight forward; we just invert the formulas for U and V above. But deriving
-// the formula for G is a bit more complicated:
-//
-// Start with equation for luminance:
-//   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
-// Solve for G:
-//   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
-// Substitute equations for R and B in terms YUV:
-//   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
-// Simplify:
-//   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
-//     + U * (kBt2100B * kBt2100Cb / kBt2100G)
-//     + V * (kBt2100R * kBt2100Cr / kBt2100G)
-//
-// We then get the following coeficients for calculating G from YUV:
-//
-// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
-// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
-// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
-
-static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
-static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
-
-Color bt2100YuvToRgb(Color e_gamma) {
-  return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
-             clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
-             clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
-static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
-
-float hlgOetf(float e) {
-  if (e <= 1.0f/12.0f) {
-    return sqrt(3.0f * e);
-  } else {
-    return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
-  }
-}
-
-Color hlgOetf(Color e) {
-  return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
-}
-
-float hlgOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
-
-  return kHlgOETF[value];
-}
-
-Color hlgOetfLUT(Color e) {
-  return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
-float hlgInvOetf(float e_gamma) {
-  if (e_gamma <= 0.5f) {
-    return pow(e_gamma, 2.0f) / 3.0f;
-  } else {
-    return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
-  }
-}
-
-Color hlgInvOetf(Color e_gamma) {
-  return {{{ hlgInvOetf(e_gamma.r),
-             hlgInvOetf(e_gamma.g),
-             hlgInvOetf(e_gamma.b) }}};
-}
-
-float hlgInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
-
-  return kHlgInvOETF[value];
-}
-
-Color hlgInvOetfLUT(Color e_gamma) {
-  return {{{ hlgInvOetfLUT(e_gamma.r),
-             hlgInvOetfLUT(e_gamma.g),
-             hlgInvOetfLUT(e_gamma.b) }}};
-}
-
-// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
-static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
-static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
-                   kPqC3 = 2392.0f / 4096.0f * 32.0f;
-
-float pqOetf(float e) {
-  if (e <= 0.0f) return 0.0f;
-  return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
-             kPqM2);
-}
-
-Color pqOetf(Color e) {
-  return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
-}
-
-float pqOetfLUT(float e) {
-  uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqOETFNumEntries - 1);
-
-  return kPqOETF[value];
-}
-
-Color pqOetfLUT(Color e) {
-  return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
-}
-
-// Derived from the inverse of the Reference PQ OETF.
-static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
-                   kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
-
-float pqInvOetf(float e_gamma) {
-  // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
-  // always catch 0.0. So, check on 0.0001, since anything this small will
-  // effectively be crushed to zero anyways.
-  if (e_gamma <= 0.0001f) return 0.0f;
-  return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
-           / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
-             kPqInvE);
-}
-
-Color pqInvOetf(Color e_gamma) {
-  return {{{ pqInvOetf(e_gamma.r),
-             pqInvOetf(e_gamma.g),
-             pqInvOetf(e_gamma.b) }}};
-}
-
-float pqInvOetfLUT(float e_gamma) {
-  uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
-  //TODO() : Remove once conversion modules have appropriate clamping in place
-  value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
-
-  return kPqInvOETF[value];
-}
-
-Color pqInvOetfLUT(Color e_gamma) {
-  return {{{ pqInvOetfLUT(e_gamma.r),
-             pqInvOetfLUT(e_gamma.g),
-             pqInvOetfLUT(e_gamma.b) }}};
-}
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color conversions
-
-Color bt709ToP3(Color e) {
- return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
-            0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
-            0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
-}
-
-Color bt709ToBt2100(Color e) {
- return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
-            0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
-            0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
-}
-
-Color p3ToBt709(Color e) {
- return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
-            -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
-            -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
-}
-
-Color p3ToBt2100(Color e) {
- return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
-            0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
-            -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
-}
-
-Color bt2100ToBt709(Color e) {
- return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
-            -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
-            -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
-}
-
-Color bt2100ToP3(Color e) {
- return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
-            -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
-            0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
- }}};
-}
-
-// TODO: confirm we always want to convert like this before calculating
-// luminance.
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
-                                    ultrahdr_color_gamut hdr_gamut) {
-  switch (sdr_gamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt709;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToBt709;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToP3;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return bt2100ToP3;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (hdr_gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return bt709ToBt2100;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return p3ToBt2100;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return identityConversion;
-        case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-          return nullptr;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      return nullptr;
-  }
-}
-
-// All of these conversions are derived from the respective input YUV->RGB conversion followed by
-// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
-// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
-// DataSpace.
-
-Color yuv709To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.101579f * e_gamma.u +  0.196076f * e_gamma.v,
-             0.0f * e_gamma.y +  0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
-             0.0f * e_gamma.y + -0.072453f * e_gamma.u +  0.983398f * e_gamma.v }}};
-}
-
-Color yuv709To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u +  0.096312f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
-             0.0f * e_gamma.y +  0.011507f * e_gamma.u +  1.002637f * e_gamma.v }}};
-}
-
-Color yuv601To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
-             0.0f * e_gamma.y +  1.018640f * e_gamma.u +  0.114618f * e_gamma.v,
-             0.0f * e_gamma.y +  0.075049f * e_gamma.u +  1.025327f * e_gamma.v }}};
-}
-
-Color yuv601To2100(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
-             0.0f * e_gamma.y +  1.010016f * e_gamma.u +  0.061592f * e_gamma.v,
-             0.0f * e_gamma.y +  0.086969f * e_gamma.u +  1.029350f * e_gamma.v }}};
-}
-
-Color yuv2100To709(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
-             0.0f * e_gamma.y +  1.004123f * e_gamma.u +  0.051267f * e_gamma.v,
-             0.0f * e_gamma.y + -0.011524f * e_gamma.u +  0.996782f * e_gamma.v }}};
-}
-
-Color yuv2100To601(Color e_gamma) {
-  return {{{ 1.0f * e_gamma.y +  0.117887f * e_gamma.u +  0.105521f * e_gamma.v,
-             0.0f * e_gamma.y +  0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
-             0.0f * e_gamma.y + -0.084085f * e_gamma.u +  0.976518f * e_gamma.v }}};
-}
-
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn) {
-  Color yuv1 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2    );
-  Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2    );
-  Color yuv3 = getYuv420Pixel(image, x_chroma * 2,     y_chroma * 2 + 1);
-  Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);
-
-  yuv1 = fn(yuv1);
-  yuv2 = fn(yuv2);
-  yuv3 = fn(yuv3);
-  yuv4 = fn(yuv4);
-
-  Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
-
-  size_t pixel_y1_idx =  x_chroma * 2      +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y2_idx = (x_chroma * 2 + 1) +  y_chroma * 2      * image->luma_stride;
-  size_t pixel_y3_idx =  x_chroma * 2      + (y_chroma * 2 + 1) * image->luma_stride;
-  size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
-
-  uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
-  uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
-  uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
-  uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
-
-  size_t pixel_count = image->chroma_stride * image->height / 2;
-  size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
-
-  uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
-  uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
-
-  y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
-  y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
-  y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
-  y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
-
-  u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
-  v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// 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;
-  }
-
-  if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
-  if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
-
-  return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
-                            / (log2MaxContentBoost - log2MinContentBoost)
-                            * 255.0f);
-}
-
-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 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) {
-  uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
-  size_t luma_stride = image->luma_stride;
-  uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  size_t offset_cr = chroma_stride * (image->height / 2);
-  size_t pixel_y_idx = x + y * luma_stride;
-  size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
-
-  uint8_t y_uint = luma_data[pixel_y_idx];
-  uint8_t u_uint = chroma_data[pixel_chroma_idx];
-  uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
-
-  // 128 bias for UV given we are using jpeglib; see:
-  // https://github.com/kornelski/libjpeg/blob/master/structure.doc
-  return {{{ static_cast<float>(y_uint) / 255.0f,
-             (static_cast<float>(u_uint) - 128.0f) / 255.0f,
-             (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
-}
-
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
-  uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
-  size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
-  uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
-  size_t chroma_stride = image->chroma_stride;
-
-  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,
-             (static_cast<float>(u_uint) - 64.0f) / 896.0f - 0.5f,
-             (static_cast<float>(v_uint) - 64.0f) / 896.0f - 0.5f }}};
-}
-
-typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
-
-static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
-                          getPixelFn get_pixel_fn) {
-  Color e = {{{ 0.0f, 0.0f, 0.0f }}};
-  for (size_t dy = 0; dy < map_scale_factor; ++dy) {
-    for (size_t dx = 0; dx < map_scale_factor; ++dx) {
-      e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
-    }
-  }
-
-  return e / static_cast<float>(map_scale_factor * map_scale_factor);
-}
-
-Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
-}
-
-Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
-}
-
-// TODO: do we need something more clever for filtering either the map or images
-// to generate the map?
-
-static size_t clamp(const size_t& val, const size_t& low, const size_t& high) {
-  return val < low ? low : (high < val ? high : val);
-}
-
-static float mapUintToFloat(uint8_t map_uint) {
-  return static_cast<float>(map_uint) / 255.0f;
-}
-
-static float pythDistance(float x_diff, float y_diff) {
-  return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f));
-}
-
-// TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
-float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) {
-  float x_map = static_cast<float>(x) / map_scale_factor;
-  float y_map = static_cast<float>(y) / map_scale_factor;
-
-  size_t x_lower = static_cast<size_t>(floor(x_map));
-  size_t x_upper = x_lower + 1;
-  size_t y_lower = static_cast<size_t>(floor(y_map));
-  size_t y_upper = y_lower + 1;
-
-  x_lower = clamp(x_lower, 0, map->width - 1);
-  x_upper = clamp(x_upper, 0, map->width - 1);
-  y_lower = clamp(y_lower, 0, map->height - 1);
-  y_upper = clamp(y_upper, 0, map->height - 1);
-
-  // Use Shepard's method for inverse distance weighting. For more information:
-  // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_lower));
-  if (e1_dist == 0.0f) return e1;
-
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
-                               y_map - static_cast<float>(y_upper));
-  if (e2_dist == 0.0f) return e2;
-
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_lower));
-  if (e3_dist == 0.0f) return e3;
-
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-  float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
-                               y_map - static_cast<float>(y_upper));
-  if (e4_dist == 0.0f) return e2;
-
-  float e1_weight = 1.0f / e1_dist;
-  float e2_weight = 1.0f / e2_dist;
-  float e3_weight = 1.0f / e3_dist;
-  float e4_weight = 1.0f / e4_dist;
-  float total_weight = e1_weight + e2_weight + e3_weight + e4_weight;
-
-  return e1 * (e1_weight / total_weight)
-       + e2 * (e2_weight / total_weight)
-       + e3 * (e3_weight / total_weight)
-       + e4 * (e4_weight / total_weight);
-}
-
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables) {
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor)
-  int x_lower = x / map_scale_factor;
-  int x_upper = x_lower + 1;
-  int y_lower = y / map_scale_factor;
-  int y_upper = y_lower + 1;
-
-  x_lower = std::min(x_lower, map->width - 1);
-  x_upper = std::min(x_upper, map->width - 1);
-  y_lower = std::min(y_lower, map->height - 1);
-  y_upper = std::min(y_upper, map->height - 1);
-
-  float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
-  float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
-  float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
-  float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
-
-  // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the
-  // following by using & (map_scale_factor - 1)
-  int offset_x = x % map_scale_factor;
-  int offset_y = y % map_scale_factor;
-
-  float* weights = weightTables.mWeights;
-  if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC;
-  else if (x_lower == x_upper) weights = weightTables.mWeightsNR;
-  else if (y_lower == y_upper) weights = weightTables.mWeightsNB;
-  weights += offset_y * map_scale_factor * 4 + offset_x * 4;
-
-  return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3];
-}
-
-uint32_t colorToRgba1010102(Color e_gamma) {
-  return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f))
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
-       | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20)
-       | (0x3 << 30);  // Set alpha to 1.0
-}
-
-uint64_t colorToRgbaF16(Color e_gamma) {
-  return (uint64_t) floatToHalf(e_gamma.r)
-       | (((uint64_t) floatToHalf(e_gamma.g)) << 16)
-       | (((uint64_t) floatToHalf(e_gamma.b)) << 32)
-       | (((uint64_t) floatToHalf(1.0f)) << 48);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/icc.cpp b/libs/ultrahdr/icc.cpp
deleted file mode 100644
index e41b645..0000000
--- a/libs/ultrahdr/icc.cpp
+++ /dev/null
@@ -1,692 +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 USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-#include <ultrahdr/icc.h>
-#include <vector>
-#include <utils/Log.h>
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-static void Matrix3x3_apply(const Matrix3x3* m, float* x) {
-    float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
-    float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
-    float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
-    x[0] = y0;
-    x[1] = y1;
-    x[2] = y2;
-}
-
-bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) {
-    double a00 = src->vals[0][0],
-           a01 = src->vals[1][0],
-           a02 = src->vals[2][0],
-           a10 = src->vals[0][1],
-           a11 = src->vals[1][1],
-           a12 = src->vals[2][1],
-           a20 = src->vals[0][2],
-           a21 = src->vals[1][2],
-           a22 = src->vals[2][2];
-
-    double b0 = a00*a11 - a01*a10,
-           b1 = a00*a12 - a02*a10,
-           b2 = a01*a12 - a02*a11,
-           b3 = a20,
-           b4 = a21,
-           b5 = a22;
-
-    double determinant = b0*b5
-                       - b1*b4
-                       + b2*b3;
-
-    if (determinant == 0) {
-        return false;
-    }
-
-    double invdet = 1.0 / determinant;
-    if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
-        return false;
-    }
-
-    b0 *= invdet;
-    b1 *= invdet;
-    b2 *= invdet;
-    b3 *= invdet;
-    b4 *= invdet;
-    b5 *= invdet;
-
-    dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
-    dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
-    dst->vals[2][0] = (float)(        +     b2 );
-    dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
-    dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
-    dst->vals[2][1] = (float)(        -     b1 );
-    dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
-    dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
-    dst->vals[2][2] = (float)(        +     b0 );
-
-    for (int r = 0; r < 3; ++r)
-    for (int c = 0; c < 3; ++c) {
-        if (!isfinitef_(dst->vals[r][c])) {
-            return false;
-        }
-    }
-    return true;
-}
-
-static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) {
-    Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
-    for (int r = 0; r < 3; r++)
-        for (int c = 0; c < 3; c++) {
-            m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
-                         + A->vals[r][1] * B->vals[1][c]
-                         + A->vals[r][2] * B->vals[2][c];
-        }
-    return m;
-}
-
-static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) {
-    float v[3] = {
-            xyz_float[0] / kD50_x,
-            xyz_float[1] / kD50_y,
-            xyz_float[2] / kD50_z,
-    };
-    for (size_t i = 0; i < 3; ++i) {
-        v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
-    }
-    const float L = v[1] * 116.0f - 16.0f;
-    const float a = (v[0] - v[1]) * 500.0f;
-    const float b = (v[1] - v[2]) * 200.0f;
-    const float Lab_unorm[3] = {
-            L * (1 / 100.f),
-            (a + 128.0f) * (1 / 255.0f),
-            (b + 128.0f) * (1 / 255.0f),
-    };
-    // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
-    // table, but the spec appears to indicate that the value should be 0xFF00.
-    // https://crbug.com/skia/13807
-    for (size_t i = 0; i < 3; ++i) {
-        reinterpret_cast<uint16_t*>(grid16_lab)[i] =
-                Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
-    }
-}
-
-std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut) {
-    std::string result;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            result += "Display P3";
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            result += "Rec2020";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Gamut with ";
-    switch (tf) {
-        case ULTRAHDR_TF_SRGB:
-            result += "sRGB";
-            break;
-        case ULTRAHDR_TF_LINEAR:
-            result += "Linear";
-            break;
-        case ULTRAHDR_TF_PQ:
-            result += "PQ";
-            break;
-        case ULTRAHDR_TF_HLG:
-            result += "HLG";
-            break;
-        default:
-            result += "Unknown";
-            break;
-    }
-    result += " Transfer";
-    return result;
-}
-
-sp<DataStruct> IccHelper::write_text_tag(const char* text) {
-    uint32_t text_length = strlen(text);
-    uint32_t header[] = {
-            Endian_SwapBE32(kTAG_TextType),                         // Type signature
-            0,                                                      // Reserved
-            Endian_SwapBE32(1),                                     // Number of records
-            Endian_SwapBE32(12),                                    // Record size (must be 12)
-            Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')),    // English USA
-            Endian_SwapBE32(2 * text_length),                       // Length of string in bytes
-            Endian_SwapBE32(28),                                    // Offset of string
-    };
-
-    uint32_t total_length = text_length * 2 + sizeof(header);
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    if (!dataStruct->write(header, sizeof(header))) {
-        ALOGE("write_text_tag(): error in writing data");
-        return dataStruct;
-    }
-
-    for (size_t i = 0; i < text_length; i++) {
-        // Convert ASCII to big-endian UTF-16.
-        dataStruct->write8(0);
-        dataStruct->write8(text[i]);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
-    uint32_t data[] = {
-            Endian_SwapBE32(kXYZ_PCSSpace),
-            0,
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
-            static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
-    };
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
-    dataStruct->write(&data, sizeof(data));
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
-    int total_length = 4 + 4 + 4 + table_entries * 2;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
-    dataStruct->write32(0);                                   // Reserved
-    dataStruct->write32(Endian_SwapBE32(table_entries));      // Value count
-    for (size_t i = 0; i < table_entries; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
-        dataStruct->write16(value);
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
-    if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
-            && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
-        int total_length = 16;
-        sp<DataStruct> dataStruct = new DataStruct(total_length);
-        dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-        dataStruct->write32(0);                                    // Reserved
-        dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
-        dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-        return dataStruct;
-    }
-
-    int total_length = 40;
-    sp<DataStruct> dataStruct = new DataStruct(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
-    dataStruct->write32(0);                                    // Reserved
-    dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
-    dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
-    return dataStruct;
-}
-
-float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
-    if (L <= 0.f) {
-        return 1.f;
-    }
-    if (tf == ULTRAHDR_TF_PQ) {
-        // The PQ transfer function will map to the range [0, 1]. Linearly scale
-        // it up to the range [0, 10,000/203]. We will then tone map that back
-        // down to [0, 1].
-        constexpr float kInputMaxLuminance = 10000 / 203.f;
-        constexpr float kOutputMaxLuminance = 1.0;
-        L *= kInputMaxLuminance;
-
-        // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
-        constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
-        constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
-        return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
-    }
-    if (tf == ULTRAHDR_TF_HLG) {
-        // Let Lw be the brightness of the display in nits.
-        constexpr float Lw = 203.f;
-        const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
-        return std::pow(L, gamma - 1.f);
-    }
-    return 1.f;
-}
-
-sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics) {
-    int total_length = 12;  // 4 + 4 + 1 + 1 + 1 + 1
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(kTAG_cicp));    // Type signature
-    dataStruct->write32(0);                             // Reserved
-    dataStruct->write8(color_primaries);                // Color primaries
-    dataStruct->write8(transfer_characteristics);       // Transfer characteristics
-    dataStruct->write8(0);                              // RGB matrix
-    dataStruct->write8(1);                              // Full range
-    return dataStruct;
-}
-
-void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) {
-    // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
-    Matrix3x3 src_to_rec2020;
-    const Matrix3x3 rec2020_to_XYZD50 = kRec2020;
-    {
-        Matrix3x3 XYZD50_to_rec2020;
-        Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
-        src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
-    }
-
-    // Convert the source signal to linear.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] = pqOetf(rgb[i]);
-    }
-
-    // Convert source gamut to Rec2020.
-    Matrix3x3_apply(&src_to_rec2020, rgb);
-
-    // Compute the luminance of the signal.
-    float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
-
-    // Compute the tone map gain based on the luminance.
-    float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L);
-
-    // Apply the tone map gain.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        rgb[i] *= tone_map_gain;
-    }
-
-    // Convert from Rec2020-linear to XYZD50.
-    Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
-}
-
-sp<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
-    uint32_t value_count = kNumChannels;
-    for (uint32_t i = 0; i < kNumChannels; ++i) {
-        value_count *= grid_points[i];
-    }
-
-    int total_length = 20 + 2 * value_count;
-    total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-
-    for (size_t i = 0; i < 16; ++i) {
-        dataStruct->write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
-    }
-    dataStruct->write8(2);  // Grid byte width (always 16-bit)
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-    dataStruct->write8(0);  // Reserved
-
-    for (uint32_t i = 0; i < value_count; ++i) {
-        uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
-        dataStruct->write16(value);
-    }
-
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16) {
-    const size_t b_curves_offset = 32;
-    sp<DataStruct> b_curves_data[kNumChannels];
-    sp<DataStruct> a_curves_data[kNumChannels];
-    size_t clut_offset = 0;
-    sp<DataStruct> clut;
-    size_t a_curves_offset = 0;
-
-    // The "B" curve is required.
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        b_curves_data[i] = write_trc_tag(kLinear_TransFun);
-    }
-
-    // The "A" curve and CLUT are optional.
-    if (has_a_curves) {
-        clut_offset = b_curves_offset;
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            clut_offset += b_curves_data[i]->getLength();
-        }
-        clut = write_clut(grid_points, grid_16);
-
-        a_curves_offset = clut_offset + clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            a_curves_data[i] = write_trc_tag(kLinear_TransFun);
-        }
-    }
-
-    int total_length = b_curves_offset;
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        total_length += b_curves_data[i]->getLength();
-    }
-    if (has_a_curves) {
-        total_length += clut->getLength();
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            total_length += a_curves_data[i]->getLength();
-        }
-    }
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
-    dataStruct->write32(Endian_SwapBE32(type));             // Type signature
-    dataStruct->write32(0);                                 // Reserved
-    dataStruct->write8(kNumChannels);                       // Input channels
-    dataStruct->write8(kNumChannels);                       // Output channels
-    dataStruct->write16(0);                                 // Reserved
-    dataStruct->write32(Endian_SwapBE32(b_curves_offset));  // B curve offset
-    dataStruct->write32(Endian_SwapBE32(0));                // Matrix offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(0));                // M curve offset (ignored)
-    dataStruct->write32(Endian_SwapBE32(clut_offset));      // CLUT offset
-    dataStruct->write32(Endian_SwapBE32(a_curves_offset));  // A curve offset
-    for (size_t i = 0; i < kNumChannels; ++i) {
-        if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) {
-            return dataStruct;
-        }
-    }
-    if (has_a_curves) {
-        dataStruct->write(clut->getData(), clut->getLength());
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength());
-        }
-    }
-    return dataStruct;
-}
-
-sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
-                                          ultrahdr_color_gamut gamut) {
-    ICCHeader header;
-
-    std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
-
-    // Compute profile description tag
-    std::string desc = get_desc_string(tf, gamut);
-
-    tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str()));
-
-    Matrix3x3 toXYZD50;
-    switch (gamut) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-            toXYZD50 = kSRGB;
-            break;
-        case ULTRAHDR_COLORGAMUT_P3:
-            toXYZD50 = kDisplayP3;
-            break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-            toXYZD50 = kRec2020;
-            break;
-        default:
-            // Should not fall here.
-            return nullptr;
-    }
-
-    // Compute primaries.
-    {
-        tags.emplace_back(kTAG_rXYZ,
-                write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0]));
-        tags.emplace_back(kTAG_gXYZ,
-                write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1]));
-        tags.emplace_back(kTAG_bXYZ,
-                write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2]));
-    }
-
-    // Compute white point tag (must be D50)
-    tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
-
-    // Compute transfer curves.
-    if (tf != ULTRAHDR_TF_PQ) {
-        if (tf == ULTRAHDR_TF_HLG) {
-            std::vector<uint8_t> trc_table;
-            trc_table.resize(kTrcTableSize * 2);
-            for (uint32_t i = 0; i < kTrcTableSize; ++i) {
-                float x = i / (kTrcTableSize - 1.f);
-                float y = hlgOetf(x);
-                y *= compute_tone_map_gain(tf, y);
-                float_to_table16(y, &trc_table[2 * i]);
-            }
-
-            tags.emplace_back(kTAG_rTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_gTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-            tags.emplace_back(kTAG_bTRC,
-                    write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
-        } else {
-            tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
-            tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
-        }
-    }
-
-    // Compute CICP.
-    if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) {
-        // The CICP tag is present in ICC 4.4, so update the header's version.
-        header.version = Endian_SwapBE32(0x04400000);
-
-        uint32_t color_primaries = 0;
-        if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
-            color_primaries = kCICPPrimariesSRGB;
-        } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
-            color_primaries = kCICPPrimariesP3;
-        }
-
-        uint32_t transfer_characteristics = 0;
-        if (tf == ULTRAHDR_TF_SRGB) {
-            transfer_characteristics = kCICPTrfnSRGB;
-        } else if (tf == ULTRAHDR_TF_LINEAR) {
-            transfer_characteristics = kCICPTrfnLinear;
-        } else if (tf == ULTRAHDR_TF_PQ) {
-            transfer_characteristics = kCICPTrfnPQ;
-        } else if (tf == ULTRAHDR_TF_HLG) {
-            transfer_characteristics = kCICPTrfnHLG;
-        }
-        tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
-    }
-
-    // Compute A2B0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        std::vector<uint8_t> a2b_grid;
-        a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
-        size_t a2b_grid_index = 0;
-        for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
-            for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
-                for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
-                    float rgb[3] = {
-                            r_index / (kGridSize - 1.f),
-                            g_index / (kGridSize - 1.f),
-                            b_index / (kGridSize - 1.f),
-                    };
-                    compute_lut_entry(toXYZD50, rgb);
-                    float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]);
-                    a2b_grid_index += 6;
-                }
-            }
-        }
-        const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
-
-        uint8_t grid_points[kNumChannels];
-        for (size_t i = 0; i < kNumChannels; ++i) {
-            grid_points[i] = kGridSize;
-        }
-
-        auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
-                                             /* has_a_curves */ true,
-                                             grid_points,
-                                             grid_16);
-        tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
-    }
-
-    // Compute B2A0.
-    if (tf == ULTRAHDR_TF_PQ) {
-        auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
-                                             /* has_a_curves */ false,
-                                             /* grid_points */ nullptr,
-                                             /* grid_16 */ nullptr);
-        tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
-    }
-
-    // Compute copyright tag
-    tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022"));
-
-    // Compute the size of the profile.
-    size_t tag_data_size = 0;
-    for (const auto& tag : tags) {
-        tag_data_size += tag.second->getLength();
-    }
-    size_t tag_table_size = kICCTagTableEntrySize * tags.size();
-    size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
-
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);
-
-    // Write identifier, chunk count, and chunk ID
-    if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
-        !dataStruct->write8(1) || !dataStruct->write8(1)) {
-        ALOGE("writeIccProfile(): error in identifier");
-        return dataStruct;
-    }
-
-    // Write the header.
-    header.data_color_space = Endian_SwapBE32(Signature_RGB);
-    header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
-    header.size = Endian_SwapBE32(profile_size);
-    header.tag_count = Endian_SwapBE32(tags.size());
-
-    if (!dataStruct->write(&header, sizeof(header))) {
-        ALOGE("writeIccProfile(): error in header");
-        return dataStruct;
-    }
-
-    // Write the tag table. Track the offset and size of the previous tag to
-    // compute each tag's offset. An empty SkData indicates that the previous
-    // tag is to be reused.
-    uint32_t last_tag_offset = sizeof(header) + tag_table_size;
-    uint32_t last_tag_size = 0;
-    for (const auto& tag : tags) {
-        last_tag_offset = last_tag_offset + last_tag_size;
-        last_tag_size = tag.second->getLength();
-        uint32_t tag_table_entry[3] = {
-                Endian_SwapBE32(tag.first),
-                Endian_SwapBE32(last_tag_offset),
-                Endian_SwapBE32(last_tag_size),
-        };
-        if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) {
-            ALOGE("writeIccProfile(): error in writing tag table");
-            return dataStruct;
-        }
-    }
-
-    // Write the tags.
-    for (const auto& tag : tags) {
-        if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) {
-            ALOGE("writeIccProfile(): error in writing tags");
-            return dataStruct;
-        }
-    }
-
-    return dataStruct;
-}
-
-bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag) {
-    sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
-                                                matrix.vals[2][0]);
-    sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
-                                                  matrix.vals[2][1]);
-    sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
-                                                 matrix.vals[2][2]);
-    return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
-           memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
-}
-
-ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
-    // Each tag table entry consists of 3 fields of 4 bytes each.
-    static const size_t kTagTableEntrySize = 12;
-
-    if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;
-
-    ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);
-
-    // Use 0 to indicate not found, since offsets are always relative to start
-    // of ICC data and therefore a tag offset of zero would never be valid.
-    size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
-    size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
-    for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
-        uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
-            icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
-        // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
-        // last 4 bytes are the tag length in bytes.
-        if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
-            red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
-            green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
-            blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
-            blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
-        }
-    }
-
-    if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
-        green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
-        blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
-        kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
-        return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    }
-
-    uint8_t* red_tag = icc_bytes + red_primary_offset;
-    uint8_t* green_tag = icc_bytes + green_primary_offset;
-    uint8_t* blue_tag = icc_bytes + blue_primary_offset;
-
-    // Serialize tags as we do on encode and compare what we find to that to
-    // determine the gamut (since we don't have a need yet for full deserialize).
-    if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT709;
-    } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_P3;
-    } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
-        return ULTRAHDR_COLORGAMUT_BT2100;
-    }
-
-    // Didn't find a match to one of the profiles we write; indicate the gamut
-    // is unspecified since we don't understand it.
-    return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/include/ultrahdr/gainmapmath.h b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
deleted file mode 100644
index 9f1238f..0000000
--- a/libs/ultrahdr/include/ultrahdr/gainmapmath.h
+++ /dev/null
@@ -1,505 +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_ULTRAHDR_RECOVERYMAPMATH_H
-#define ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
-
-#include <cmath>
-#include <stdint.h>
-
-#include <ultrahdr/jpegr.h>
-
-namespace android::ultrahdr {
-
-#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
-
-////////////////////////////////////////////////////////////////////////////////
-// Framework
-
-const float kSdrWhiteNits = 100.0f;
-const float kHlgMaxNits = 1000.0f;
-const float kPqMaxNits = 10000.0f;
-
-struct Color {
-  union {
-    struct {
-      float r;
-      float g;
-      float b;
-    };
-    struct {
-      float y;
-      float u;
-      float v;
-    };
-  };
-};
-
-typedef Color (*ColorTransformFn)(Color);
-typedef float (*ColorCalculationFn)(Color);
-
-// A transfer function mapping encoded values to linear values,
-// represented by this 7-parameter piecewise function:
-//
-//   linear = sign(encoded) *  (c*|encoded| + f)       , 0 <= |encoded| < d
-//          = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
-//
-// (A simple gamma transfer function sets g to gamma and a to 1.)
-typedef struct TransferFunction {
-    float g, a,b,c,d,e,f;
-} TransferFunction;
-
-static constexpr TransferFunction kSRGB_TransFun =
-    { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
-
-static constexpr TransferFunction kLinear_TransFun =
-    { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
-
-inline Color operator+=(Color& lhs, const Color& rhs) {
-  lhs.r += rhs.r;
-  lhs.g += rhs.g;
-  lhs.b += rhs.b;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const Color& rhs) {
-  lhs.r -= rhs.r;
-  lhs.g -= rhs.g;
-  lhs.b -= rhs.b;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const Color& rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-
-inline Color operator+=(Color& lhs, const float rhs) {
-  lhs.r += rhs;
-  lhs.g += rhs;
-  lhs.b += rhs;
-  return lhs;
-}
-inline Color operator-=(Color& lhs, const float rhs) {
-  lhs.r -= rhs;
-  lhs.g -= rhs;
-  lhs.b -= rhs;
-  return lhs;
-}
-inline Color operator*=(Color& lhs, const float rhs) {
-  lhs.r *= rhs;
-  lhs.g *= rhs;
-  lhs.b *= rhs;
-  return lhs;
-}
-inline Color operator/=(Color& lhs, const float rhs) {
-  lhs.r /= rhs;
-  lhs.g /= rhs;
-  lhs.b /= rhs;
-  return lhs;
-}
-
-inline Color operator+(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp += rhs;
-}
-inline Color operator-(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp -= rhs;
-}
-inline Color operator*(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp *= rhs;
-}
-inline Color operator/(const Color& lhs, const float rhs) {
-  Color temp = lhs;
-  return temp /= rhs;
-}
-
-inline uint16_t floatToHalf(float f) {
-  // 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 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;
-      mGainTable[idx] = exp2(logBoost);
-    }
-  }
-
-  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);
-    }
-  }
-
-  ~GainLUT() {
-  }
-
-  float getGainFactor(float gain) {
-    uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
-    //TODO() : Remove once conversion modules have appropriate clamping in place
-    idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
-    return mGainTable[idx];
-  }
-
-private:
-  float mGainTable[kGainFactorNumEntries];
-};
-
-struct ShepardsIDW {
-  ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
-    const int size = mMapScaleFactor * mMapScaleFactor * 4;
-    mWeights = new float[size];
-    mWeightsNR = new float[size];
-    mWeightsNB = new float[size];
-    mWeightsC = new float[size];
-    fillShepardsIDW(mWeights, 1, 1);
-    fillShepardsIDW(mWeightsNR, 0, 1);
-    fillShepardsIDW(mWeightsNB, 1, 0);
-    fillShepardsIDW(mWeightsC, 0, 0);
-  }
-  ~ShepardsIDW() {
-    delete[] mWeights;
-    delete[] mWeightsNR;
-    delete[] mWeightsNB;
-    delete[] mWeightsC;
-  }
-
-  int mMapScaleFactor;
-  // Image :-
-  // p00 p01 p02 p03 p04 p05 p06 p07
-  // p10 p11 p12 p13 p14 p15 p16 p17
-  // p20 p21 p22 p23 p24 p25 p26 p27
-  // p30 p31 p32 p33 p34 p35 p36 p37
-  // p40 p41 p42 p43 p44 p45 p46 p47
-  // p50 p51 p52 p53 p54 p55 p56 p57
-  // p60 p61 p62 p63 p64 p65 p66 p67
-  // p70 p71 p72 p73 p74 p75 p76 p77
-
-  // Gain Map (for 4 scale factor) :-
-  // m00 p01
-  // m10 m11
-
-  // 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
-  float* mWeightsNR;  // no right
-  float* mWeightsNB;  // no bottom
-  float* mWeightsC;  // no right & bottom
-
-  float euclideanDistance(float x1, float x2, float y1, float y2);
-  void fillShepardsIDW(float *weights, int incR, int incB);
-};
-
-////////////////////////////////////////////////////////////////////////////////
-// sRGB transformations
-// NOTE: sRGB has the same color primaries as BT.709, but different transfer
-// function. For this reason, all sRGB transformations here apply to BT.709,
-// except for those concerning transfer functions.
-
-/*
- * Calculate the luminance of a linear RGB sRGB pixel, according to
- * IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbLuminance(Color e);
-
-/*
- * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbRgbToYuv(Color e_gamma);
-
-
-/*
- * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
- *
- * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color srgbYuvToRgb(Color e_gamma);
-
-/*
- * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
- *
- * [0.0, 1.0] range in and out.
- */
-float srgbInvOetf(float e_gamma);
-Color srgbInvOetf(Color e_gamma);
-float srgbInvOetfLUT(float e_gamma);
-Color srgbInvOetfLUT(Color e_gamma);
-
-constexpr size_t kSrgbInvOETFPrecision = 10;
-constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
-
-////////////////////////////////////////////////////////////////////////////////
-// Display-P3 transformations
-
-/*
- * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
- *
- * [0.0, 1.0] range in and out.
- */
-float p3Luminance(Color e);
-
-/*
- * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
- *
- * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color p3YuvToRgb(Color e_gamma);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// BT.2100 transformations - according to ITU-R BT.2100-2
-
-/*
- * Calculate the luminance of a linear RGB BT.2100 pixel.
- *
- * [0.0, 1.0] range in and out.
- */
-float bt2100Luminance(Color e);
-
-/*
- * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100RgbToYuv(Color e_gamma);
-
-/*
- * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
- *
- * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
- */
-Color bt2100YuvToRgb(Color e_gamma);
-
-/*
- * Convert from scene luminance to HLG.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgOetf(float e);
-Color hlgOetf(Color e);
-float hlgOetfLUT(float e);
-Color hlgOetfLUT(Color e);
-
-constexpr size_t kHlgOETFPrecision = 16;
-constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
-
-/*
- * Convert from HLG to scene luminance.
- *
- * [0.0, 1.0] range in and out.
- */
-float hlgInvOetf(float e_gamma);
-Color hlgInvOetf(Color e_gamma);
-float hlgInvOetfLUT(float e_gamma);
-Color hlgInvOetfLUT(Color e_gamma);
-
-constexpr size_t kHlgInvOETFPrecision = 12;
-constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
-
-/*
- * Convert from scene luminance to PQ.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqOetf(float e);
-Color pqOetf(Color e);
-float pqOetfLUT(float e);
-Color pqOetfLUT(Color e);
-
-constexpr size_t kPqOETFPrecision = 16;
-constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
-
-/*
- * Convert from PQ to scene luminance in nits.
- *
- * [0.0, 1.0] range in and out.
- */
-float pqInvOetf(float e_gamma);
-Color pqInvOetf(Color e_gamma);
-float pqInvOetfLUT(float e_gamma);
-Color pqInvOetfLUT(Color e_gamma);
-
-constexpr size_t kPqInvOETFPrecision = 12;
-constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Color space conversions
-
-/*
- * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
- *
- * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
- * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
- * always the inverse of the RGB gamut to XYZ matrix.
- */
-Color bt709ToP3(Color e);
-Color bt709ToBt2100(Color e);
-Color p3ToBt709(Color e);
-Color p3ToBt2100(Color e);
-Color bt2100ToBt709(Color e);
-Color bt2100ToP3(Color e);
-
-/*
- * Identity conversion.
- */
-inline Color identityConversion(Color e) { return e; }
-
-/*
- * Get the conversion to apply to the HDR image for gain map generation
- */
-ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
-
-/*
- * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
- *
- * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
- * treated as Bt.601 by DataSpace, hence we do the same.
- */
-Color yuv709To601(Color e_gamma);
-Color yuv709To2100(Color e_gamma);
-Color yuv601To709(Color e_gamma);
-Color yuv601To2100(Color e_gamma);
-Color yuv2100To709(Color e_gamma);
-Color yuv2100To601(Color e_gamma);
-
-/*
- * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
- *
- * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
- * this result, and UV gets the averaged result.
- *
- * x_chroma and y_chroma should be less than or equal to half the image's width and height
- * respecitively, since input is 4:2:0 subsampled.
- */
-void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
-                     ColorTransformFn fn);
-
-
-////////////////////////////////////////////////////////////////////////////////
-// Gain map calculations
-
-/*
- * 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.
- *
- * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
- * offsetHdr of 0.0, this function doesn't handle different metadata values for
- * these fields.
- */
-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 gain
- * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
- *
- * Note: similar to encodeGain(), this function only supports gamma 1.0,
- * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
- * gainMapMax, as this library encodes.
- */
-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.
- */
-Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Helper for sampling from P010 images.
- *
- * Expect narrow-range image data for P010.
- */
-Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- */
-Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * Sample the image at the provided location, with a weighting based on nearby
- * pixels and the map scale factor.
- *
- * Expect narrow-range image data for P010.
- */
-Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-
-/*
- * 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);
-float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
-                ShepardsIDW& weightTables);
-
-/*
- * Convert from Color to RGBA1010102.
- *
- * Alpha always set to 1.0.
- */
-uint32_t colorToRgba1010102(Color e_gamma);
-
-/*
- * Convert from Color to F16.
- *
- * Alpha always set to 1.0.
- */
-uint64_t colorToRgbaF16(Color e_gamma);
-
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
deleted file mode 100644
index 971b267..0000000
--- a/libs/ultrahdr/include/ultrahdr/icc.h
+++ /dev/null
@@ -1,256 +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_ULTRAHDR_ICC_H
-#define ANDROID_ULTRAHDR_ICC_H
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <utils/RefBase.h>
-#include <cmath>
-#include <string>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-typedef int32_t              Fixed;
-#define Fixed1               (1 << 16)
-#define MaxS32FitsInFloat    2147483520
-#define MinS32FitsInFloat    (-MaxS32FitsInFloat)
-#define FixedToFloat(x)      ((x) * 1.52587890625e-5f)
-
-typedef struct Matrix3x3 {
-    float vals[3][3];
-} Matrix3x3;
-
-// The D50 illuminant.
-constexpr float kD50_x = 0.9642f;
-constexpr float kD50_y = 1.0000f;
-constexpr float kD50_z = 0.8249f;
-
-enum {
-    // data_color_space
-    Signature_CMYK = 0x434D594B,
-    Signature_Gray = 0x47524159,
-    Signature_RGB  = 0x52474220,
-
-    // pcs
-    Signature_Lab  = 0x4C616220,
-    Signature_XYZ  = 0x58595A20,
-};
-
-typedef uint32_t FourByteTag;
-static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) {
-    return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d);
-}
-
-static constexpr char kICCIdentifier[] = "ICC_PROFILE";
-// 12 for the actual identifier, +2 for the chunk count and chunk index which
-// will always follow.
-static constexpr size_t kICCIdentifierSize = 14;
-
-// This is equal to the header size according to the ICC specification (128)
-// plus the size of the tag count (4).  We include the tag count since we
-// always require it to be present anyway.
-static constexpr size_t kICCHeaderSize = 132;
-
-// Contains a signature (4), offset (4), and size (4).
-static constexpr size_t kICCTagTableEntrySize = 12;
-
-// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12
-// bytes for a single XYZ number type (4 bytes per coordinate).
-static constexpr size_t kColorantTagSize = 20;
-
-static constexpr uint32_t kDisplay_Profile    = SetFourByteTag('m', 'n', 't', 'r');
-static constexpr uint32_t kRGB_ColorSpace     = SetFourByteTag('R', 'G', 'B', ' ');
-static constexpr uint32_t kXYZ_PCSSpace       = SetFourByteTag('X', 'Y', 'Z', ' ');
-static constexpr uint32_t kACSP_Signature     = SetFourByteTag('a', 'c', 's', 'p');
-
-static constexpr uint32_t kTAG_desc           = SetFourByteTag('d', 'e', 's', 'c');
-static constexpr uint32_t kTAG_TextType       = SetFourByteTag('m', 'l', 'u', 'c');
-static constexpr uint32_t kTAG_rXYZ           = SetFourByteTag('r', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_gXYZ           = SetFourByteTag('g', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_bXYZ           = SetFourByteTag('b', 'X', 'Y', 'Z');
-static constexpr uint32_t kTAG_wtpt           = SetFourByteTag('w', 't', 'p', 't');
-static constexpr uint32_t kTAG_rTRC           = SetFourByteTag('r', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_gTRC           = SetFourByteTag('g', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_bTRC           = SetFourByteTag('b', 'T', 'R', 'C');
-static constexpr uint32_t kTAG_cicp           = SetFourByteTag('c', 'i', 'c', 'p');
-static constexpr uint32_t kTAG_cprt           = SetFourByteTag('c', 'p', 'r', 't');
-static constexpr uint32_t kTAG_A2B0           = SetFourByteTag('A', '2', 'B', '0');
-static constexpr uint32_t kTAG_B2A0           = SetFourByteTag('B', '2', 'A', '0');
-
-static constexpr uint32_t kTAG_CurveType      = SetFourByteTag('c', 'u', 'r', 'v');
-static constexpr uint32_t kTAG_mABType        = SetFourByteTag('m', 'A', 'B', ' ');
-static constexpr uint32_t kTAG_mBAType        = SetFourByteTag('m', 'B', 'A', ' ');
-static constexpr uint32_t kTAG_ParaCurveType  = SetFourByteTag('p', 'a', 'r', 'a');
-
-
-static constexpr Matrix3x3 kSRGB = {{
-    // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync.
-    // 0.436065674f, 0.385147095f, 0.143066406f,
-    // 0.222488403f, 0.716873169f, 0.060607910f,
-    // 0.013916016f, 0.097076416f, 0.714096069f,
-    { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) },
-    { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) },
-    { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) },
-}};
-
-static constexpr Matrix3x3 kDisplayP3 = {{
-    {  0.515102f,   0.291965f,  0.157153f  },
-    {  0.241182f,   0.692236f,  0.0665819f },
-    { -0.00104941f, 0.0418818f, 0.784378f  },
-}};
-
-static constexpr Matrix3x3 kRec2020 = {{
-    {  0.673459f,   0.165661f,  0.125100f  },
-    {  0.279033f,   0.675338f,  0.0456288f },
-    { -0.00193139f, 0.0299794f, 0.797162f  },
-}};
-
-static constexpr uint32_t kCICPPrimariesSRGB = 1;
-static constexpr uint32_t kCICPPrimariesP3 = 12;
-static constexpr uint32_t kCICPPrimariesRec2020 = 9;
-
-static constexpr uint32_t kCICPTrfnSRGB = 1;
-static constexpr uint32_t kCICPTrfnLinear = 8;
-static constexpr uint32_t kCICPTrfnPQ = 16;
-static constexpr uint32_t kCICPTrfnHLG = 18;
-
-enum ParaCurveType {
-    kExponential_ParaCurveType = 0,
-    kGAB_ParaCurveType         = 1,
-    kGABC_ParaCurveType        = 2,
-    kGABDE_ParaCurveType       = 3,
-    kGABCDEF_ParaCurveType     = 4,
-};
-
-/**
- *  Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN.
- */
-static inline int float_saturate2int(float x) {
-    x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat;
-    x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat;
-    return (int)x;
-}
-
-static Fixed float_round_to_fixed(float x) {
-    return float_saturate2int((float)floor((double)x * Fixed1 + 0.5));
-}
-
-static uint16_t float_round_to_unorm16(float x) {
-    x = x * 65535.f + 0.5;
-    if (x > 65535) return 65535;
-    if (x < 0) return 0;
-    return static_cast<uint16_t>(x);
-}
-
-static void float_to_table16(const float f, uint8_t* table_16) {
-    *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f));
-}
-
-static bool isfinitef_(float x) { return 0 == x*0; }
-
-struct ICCHeader {
-    // Size of the profile (computed)
-    uint32_t size;
-    // Preferred CMM type (ignored)
-    uint32_t cmm_type = 0;
-    // Version 4.3 or 4.4 if CICP is included.
-    uint32_t version = Endian_SwapBE32(0x04300000);
-    // Display device profile
-    uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile);
-    // RGB input color space;
-    uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace);
-    // Profile connection space.
-    uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace);
-    // Date and time (ignored)
-    uint8_t creation_date_time[12] = {0};
-    // Profile signature
-    uint32_t signature = Endian_SwapBE32(kACSP_Signature);
-    // Platform target (ignored)
-    uint32_t platform = 0;
-    // Flags: not embedded, can be used independently
-    uint32_t flags = 0x00000000;
-    // Device manufacturer (ignored)
-    uint32_t device_manufacturer = 0;
-    // Device model (ignored)
-    uint32_t device_model = 0;
-    // Device attributes (ignored)
-    uint8_t device_attributes[8] = {0};
-    // Relative colorimetric rendering intent
-    uint32_t rendering_intent = Endian_SwapBE32(1);
-    // D50 standard illuminant (X, Y, Z)
-    uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x));
-    uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y));
-    uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z));
-    // Profile creator (ignored)
-    uint32_t creator = 0;
-    // Profile id checksum (ignored)
-    uint8_t profile_id[16] = {0};
-    // Reserved (ignored)
-    uint8_t reserved[28] = {0};
-    // Technically not part of header, but required
-    uint32_t tag_count = 0;
-};
-
-class IccHelper {
-private:
-    static constexpr uint32_t kTrcTableSize = 65;
-    static constexpr uint32_t kGridSize = 17;
-    static constexpr size_t kNumChannels = 3;
-
-    static sp<DataStruct> write_text_tag(const char* text);
-    static std::string get_desc_string(const ultrahdr_transfer_function tf,
-                                       const ultrahdr_color_gamut gamut);
-    static sp<DataStruct> write_xyz_tag(float x, float y, float z);
-    static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
-    static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
-    static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
-    static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
-                                         uint32_t transfer_characteristics);
-    static sp<DataStruct> write_mAB_or_mBA_tag(uint32_t type,
-                                               bool has_a_curves,
-                                               const uint8_t* grid_points,
-                                               const uint8_t* grid_16);
-    static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]);
-    static sp<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16);
-
-    // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input
-    // tag buffer assumed to be at least kColorantTagSize in size.
-    static bool tagsEqualToMatrix(const Matrix3x3& matrix,
-                                  const uint8_t* red_tag,
-                                  const uint8_t* green_tag,
-                                  const uint8_t* blue_tag);
-
-public:
-    // Output includes JPEG embedding identifier and chunk information, but not
-    // APPx information.
-    static sp<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf,
-                                          const ultrahdr_color_gamut gamut);
-    // NOTE: this function is not robust; it can infer gamuts that IccHelper
-    // writes out but should not be considered a reference implementation for
-    // robust parsing of ICC profiles or their gamuts.
-    static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size);
-};
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ICC_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
deleted file mode 100644
index b86ce5f4..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ /dev/null
@@ -1,143 +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_ULTRAHDR_JPEGDECODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-#include <utils/Errors.h>
-#include <vector>
-
-// constraint on max width and max height is only due to device alloc constraints
-// Can tune these values basing on the target device
-static const int kMaxWidth = 8192;
-static const int kMaxHeight = 8192;
-
-namespace android::ultrahdr {
-/*
- * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
- * This class is not thread-safe.
- */
-class JpegDecoderHelper {
-public:
-    JpegDecoderHelper();
-    ~JpegDecoderHelper();
-    /*
-     * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
-     * calling this method, call getDecompressedImage() to get the image.
-     * Returns false if decompressing the image fails.
-     */
-    bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
-    /*
-     * Returns the decompressed raw image buffer pointer. This method must be called only after
-     * calling decompressImage().
-     */
-    void* getDecompressedImagePtr();
-    /*
-     * Returns the decompressed raw image buffer size. This method must be called only after
-     * calling decompressImage().
-     */
-    size_t getDecompressedImageSize();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageWidth();
-    /*
-     * Returns the image width in pixels. This method must be called only after calling
-     * decompressImage().
-     */
-    size_t getDecompressedImageHeight();
-    /*
-     * Returns the XMP data from the image.
-     */
-    void* getXMPPtr();
-    /*
-     * Returns the decompressed XMP buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getXMPSize();
-    /*
-     * Extracts EXIF package and updates the EXIF position / length without decoding the image.
-     */
-    bool extractEXIF(const void* image, int length);
-    /*
-     * Returns the EXIF data from the image.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    void* getEXIFPtr();
-    /*
-     * Returns the decompressed EXIF buffer size. This method must be called only after
-     * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
-     */
-    size_t getEXIFSize();
-    /*
-     * Returns the position offset of EXIF package
-     * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
-     * or -1  if no EXIF exists.
-     * This method must be called after extractEXIF() or decompressImage().
-     */
-    int getEXIFPos() { return mExifPos; }
-    /*
-     * Returns the ICC data from the image.
-     */
-    void* getICCPtr();
-    /*
-     * Returns the decompressed ICC buffer size. This method must be called only after
-     * calling decompressImage() or getCompressedImageParameters().
-     */
-    size_t getICCSize();
-    /*
-     * Decompresses metadata of the image. All vectors are owned by the caller.
-     */
-    bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                      size_t* pHeight, std::vector<uint8_t>* iccData,
-                                      std::vector<uint8_t>* exifData);
-
-private:
-    bool decode(const void* image, int length, bool decodeToRGBA);
-    // Returns false if errors occur.
-    bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
-    bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
-    // 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 decompressed result.
-    std::vector<JOCTET> mResultBuffer;
-    // The buffer that holds XMP Data.
-    std::vector<JOCTET> mXMPBuffer;
-    // The buffer that holds EXIF Data.
-    std::vector<JOCTET> mEXIFBuffer;
-    // The buffer that holds ICC Data.
-    std::vector<JOCTET> mICCBuffer;
-
-    // Resolution of the decompressed image.
-    size_t mWidth;
-    size_t mHeight;
-
-    // Position of EXIF package, default value is -1 which means no EXIF package appears.
-    ssize_t mExifPos = -1;
-};
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
deleted file mode 100644
index 9d06415..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
+++ /dev/null
@@ -1,102 +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_ULTRAHDR_JPEGENCODERHELPER_H
-#define ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
-
-// We must include cstdio before jpeglib.h. It is a requirement of libjpeg.
-#include <cstdio>
-#include <vector>
-
-extern "C" {
-#include <jerror.h>
-#include <jpeglib.h>
-}
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-/*
- * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
- * This class is not thread-safe.
- */
-class JpegEncoderHelper {
-public:
-    JpegEncoderHelper();
-    ~JpegEncoderHelper();
-
-    /*
-     * Compresses YUV420Planer image to JPEG format. After calling this method, call
-     * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
-     * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
-     * ICC segment which will be added to the compressed image.
-     * Returns false if errors occur during compression.
-     */
-    bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                       int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                       unsigned int iccSize);
-
-    /*
-     * Returns the compressed JPEG buffer pointer. This method must be called only after calling
-     * compressImage().
-     */
-    void* getCompressedImagePtr();
-
-    /*
-     * Returns the compressed JPEG buffer size. This method must be called only after calling
-     * compressImage().
-     */
-    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.
-    static void initDestination(j_compress_ptr cinfo);
-    static boolean emptyOutputBuffer(j_compress_ptr cinfo);
-    static void terminateDestination(j_compress_ptr cinfo);
-    static void outputErrorMessage(j_common_ptr cinfo);
-
-    // Returns false if errors occur.
-    bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
-                int lumaStride, int chromaStride, int quality, const void* iccBuffer,
-                unsigned int iccSize);
-    void setJpegDestination(jpeg_compress_struct* cinfo);
-    void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
-                               bool isSingleChannel);
-    // Returns false if errors occur.
-    bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
-                     int lumaStride, int chromaStride);
-    bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
-
-    // The block size for encoded jpeg image buffer.
-    static const int kBlockSize = 16384;
-
-    // The buffer that holds the compressed result.
-    std::vector<JOCTET> mResultBuffer;
-};
-
-} /* namespace android::ultrahdr  */
-
-#endif // ANDROID_ULTRAHDR_JPEGENCODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
deleted file mode 100644
index 114c81d..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ /dev/null
@@ -1,452 +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_ULTRAHDR_JPEGR_H
-#define ANDROID_ULTRAHDR_JPEGR_H
-
-#include <cstdint>
-#include <vector>
-
-#include "ultrahdr/jpegdecoderhelper.h"
-#include "ultrahdr/jpegencoderhelper.h"
-#include "ultrahdr/jpegrerrorcode.h"
-#include "ultrahdr/ultrahdr.h"
-
-#ifndef FLT_MAX
-#define FLT_MAX 0x1.fffffep127f
-#endif
-
-namespace android::ultrahdr {
-
-// The current JPEGR version that we encode to
-static const char* const kJpegrVersion = "1.0";
-
-// Map is quarter res / sixteenth size
-static const size_t kMapDimensionScaleFactor = 4;
-
-// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
-// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
-// 1 sample is sufficient. We are using 2 here anyways
-static const int kMinWidth = 2 * kMapDimensionScaleFactor;
-static const int kMinHeight = 2 * kMapDimensionScaleFactor;
-
-// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
-// JpegEncoderHelper::kCompressBatchSize.
-// The width and height of image under compression is expected to be a multiple of MCU size.
-// If this criteria is not satisfied, padding is done.
-static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
-
-/*
- * Holds information of jpegr image
- */
-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
-    // after the luma plane.
-    void* chroma_data = nullptr;
-    // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
-    // non-zero this value must be larger than or equal to luma width. If stride is
-    // uninitialized then it is assumed to be equal to luma width.
-    int luma_stride = 0;
-    // Stride of UV plane in number of pixels.
-    // 1. If this handle points to P010 image then this value must be larger than
-    //    or equal to luma width.
-    // 2. If this handle points to 420 image then this value must be larger than
-    //    or equal to (luma width / 2).
-    // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
-    // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
-    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 p010_image_ptr 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 destination 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 p010_image_ptr, 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. SDR input is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr 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 p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         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. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
-     * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
-     * transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     *                            Note: the compressed SDR image must be the compressed
-     *                                  yuv420_image_ptr image in JPEG 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}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
-                         jr_compressed_ptr yuv420jpg_image_ptr, 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. Adds an
-     * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
-     * resolution. JPEG image is assumed to use the sRGB transfer function.
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg 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}.
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
-                         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. Adds an ICC
-     * profile if one isn't present in the input JPEG image.
-     * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
-     * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
-     * @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 yuv420jpg_image_ptr,
-                         jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                         jr_compressed_ptr dest);
-
-    /*
-     * Decode API
-     * Decompress JPEGR image.
-     *
-     * This method assumes that the JPEGR image contains an ICC profile with primaries that match
-     * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also
-     * assumes the base image uses the sRGB transfer function.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     * @param jpegr_image_ptr 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 gainmap_image_ptr 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 jpegr_image_ptr, 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 gainmap_image_ptr = nullptr,
-                         ultrahdr_metadata_ptr metadata = nullptr);
-
-    /*
-     * Gets Info from JPEGR file without decoding it.
-     *
-     * This method only supports single gain map metadata values for fields that allow multi-channel
-     * metadata values.
-     *
-     * The output is filled jpegr_info structure
-     * @param jpegr_image_ptr compressed JPEGR image
-     * @param jpeg_image_info_ptr pointer to jpegr info struct. 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 jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
-
-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. The SDR input is assumed to use the sRGB transfer function.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param hdr_tf transfer function of the HDR image
-     * @param metadata everything but "version" is filled in this struct
-     * @param dest location at which gain map image is stored (caller responsible for memory
-                   of data).
-     * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                             ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
-                             bool sdr_is_601 = false);
-
-    /*
-     * 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.
-     * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
-     * be a decoded JPEG for the purpose of YUV interpration.
-     *
-     * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
-     * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
-     * @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 yuv420_image_ptr,
-                          jr_uncompressed_ptr gainmap_image_ptr, 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 gainmap_image_ptr pointer to uncompressed gain map image struct
-     * @param jpeg_enc_obj_ptr helper resource to compress gain map
-     * @return NO_ERROR if encoding succeeds, error code if error occurs.
-     */
-    status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                             JpegEncoderHelper* jpeg_enc_obj_ptr);
-
-    /*
-     * This method is called to separate primary image and gain map image from JPEGR
-     *
-     * @param jpegr_image_ptr pointer to compressed JPEGR image.
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                           jr_compressed_ptr primary_jpg_image_ptr,
-                                           jr_compressed_ptr gainmap_jpg_image_ptr);
-
-    /*
-     * 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 in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
-     * conditions is fulfilled:
-     *  (1) EXIF package is available from outside input. I.e. pExif != nullptr.
-     *  (2) Input JPEG has EXIF.
-     * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
-     *
-     * @param primary_jpg_image_ptr destination of primary image
-     * @param gainmap_jpg_image_ptr destination of compressed gain map image
-     * @param (nullable) pExif EXIF package
-     * @param (nullable) pIcc ICC package
-     * @param icc_size length in bytes of ICC 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 primary_jpg_image_ptr,
-                           jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
-                           size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
-
-    /*
-     * This method will tone map a HDR image to an SDR image.
-     *
-     * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
-     *            in p010 color format
-     * @param dest pointer to store tonemapped SDR image
-     */
-    status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
-
-    /*
-     * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
-     * Bt.709 to Bt.601 YUV encoding).
-     *
-     * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that
-     * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data.
-     *
-     * @param image the YUV420 image to convert
-     * @param src_encoding input YUV encoding
-     * @param dest_encoding output YUV encoding
-     * @return NO_ERROR if calculation succeeds, error code if error occurs.
-     */
-    status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                        ultrahdr_color_gamut dest_encoding);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p 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}.
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
-
-    /*
-     * This method will check the validity of the input arguments.
-     *
-     * @param p010_image_ptr uncompressed HDR image in P010 color format
-     * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
-     *                         be in 420p 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 destination 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
-     * @return NO_ERROR if the input args are valid, error code is not valid.
-     */
-    status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                    jr_uncompressed_ptr yuv420_image_ptr,
-                                    ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
-                                    int quality);
-};
-} // namespace android::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
deleted file mode 100644
index 5420e1c..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
+++ /dev/null
@@ -1,61 +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.
- */
-
-#ifndef ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-#define ANDROID_ULTRAHDR_JPEGRERRORCODE_H
-
-#include <utils/Errors.h>
-
-namespace android::ultrahdr {
-
-enum {
-    // status_t map for errors in the media framework
-    // OK or NO_ERROR or 0 represents no error.
-
-    // See system/core/include/utils/Errors.h
-    // System standard errors from -1 through (possibly) -133
-    //
-    // Errors with special meanings and side effects.
-    // INVALID_OPERATION:  Operation attempted in an illegal state (will try to signal to app).
-    // DEAD_OBJECT:        Signal from CodecBase to MediaCodec that MediaServer has died.
-    // NAME_NOT_FOUND:     Signal from CodecBase to MediaCodec that the component was not found.
-
-    // JPEGR errors
-    JPEGR_IO_ERROR_BASE                 = -10000,
-    ERROR_JPEGR_INVALID_INPUT_TYPE      = JPEGR_IO_ERROR_BASE,
-    ERROR_JPEGR_INVALID_OUTPUT_TYPE     = JPEGR_IO_ERROR_BASE - 1,
-    ERROR_JPEGR_INVALID_NULL_PTR        = JPEGR_IO_ERROR_BASE - 2,
-    ERROR_JPEGR_RESOLUTION_MISMATCH     = JPEGR_IO_ERROR_BASE - 3,
-    ERROR_JPEGR_BUFFER_TOO_SMALL        = JPEGR_IO_ERROR_BASE - 4,
-    ERROR_JPEGR_INVALID_COLORGAMUT      = JPEGR_IO_ERROR_BASE - 5,
-    ERROR_JPEGR_INVALID_TRANS_FUNC      = JPEGR_IO_ERROR_BASE - 6,
-    ERROR_JPEGR_INVALID_METADATA        = JPEGR_IO_ERROR_BASE - 7,
-    ERROR_JPEGR_UNSUPPORTED_METADATA    = JPEGR_IO_ERROR_BASE - 8,
-    ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9,
-
-    JPEGR_RUNTIME_ERROR_BASE            = -20000,
-    ERROR_JPEGR_ENCODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 1,
-    ERROR_JPEGR_DECODE_ERROR            = JPEGR_RUNTIME_ERROR_BASE - 2,
-    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::ultrahdr
-
-#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
deleted file mode 100644
index 4ab664e..0000000
--- a/libs/ultrahdr/include/ultrahdr/jpegrutils.h
+++ /dev/null
@@ -1,167 +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_ULTRAHDR_JPEGRUTILS_H
-#define ANDROID_ULTRAHDR_JPEGRUTILS_H
-
-#include <ultrahdr/jpegr.h>
-#include <utils/RefBase.h>
-
-#include <sstream>
-#include <stdint.h>
-#include <string>
-#include <cstdio>
-
-namespace android::ultrahdr {
-
-static constexpr uint32_t EndianSwap32(uint32_t value) {
-    return ((value & 0xFF) << 24) |
-           ((value & 0xFF00) << 8) |
-           ((value & 0xFF0000) >> 8) |
-           (value >> 24);
-}
-static inline uint16_t EndianSwap16(uint16_t value) {
-    return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8));
-}
-
-#if USE_BIG_ENDIAN
-    #define Endian_SwapBE32(n) EndianSwap32(n)
-    #define Endian_SwapBE16(n) EndianSwap16(n)
-#else
-    #define Endian_SwapBE32(n) (n)
-    #define Endian_SwapBE16(n) (n)
-#endif
-
-struct ultrahdr_metadata_struct;
-/*
- * Mutable data structure. Holds information for metadata.
- */
-class DataStruct : public RefBase {
-private:
-    void* data;
-    int writePos;
-    int length;
-    ~DataStruct();
-
-public:
-    DataStruct(int s);
-    void* getData();
-    int getLength();
-    int getBytesWritten();
-    bool write8(uint8_t value);
-    bool write16(uint16_t value);
-    bool write32(uint32_t value);
-    bool write(const void* src, int size);
-};
-
-/*
- * Helper function used for writing data to destination.
- *
- * @param destination destination of the data to be written.
- * @param source source of data being written.
- * @param length length of the data to be written.
- * @param position cursor in desitination where the data is to be written.
- * @return status of succeed or error code.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position);
-
-
-/*
- * Parses XMP packet and fills metadata with data from XMP
- *
- * @param xmp_data pointer to XMP packet
- * @param xmp_size size of XMP packet
- * @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, ultrahdr_metadata_struct* metadata);
-
-/*
- * This method generates XMP metadata for the primary image.
- *
- * below is an example of the XMP metadata that this function generates where
- * secondary_image_length = 1000
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     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:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1">
- *       <Container:Directory>
- *         <rdf:Seq>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="Primary"
- *               Item:Mime="image/jpeg"/>
- *           </rdf:li>
- *           <rdf:li
- *             rdf:parseType="Resource">
- *             <Container:Item
- *               Item:Semantic="GainMap"
- *               Item:Mime="image/jpeg"
- *               Item:Length="1000"/>
- *           </rdf:li>
- *         </rdf:Seq>
- *       </Container:Directory>
- *     </rdf:Description>
- *   </rdf:RDF>
- * </x:xmpmeta>
- *
- * @param secondary_image_length length of secondary image
- * @return XMP metadata in type of string
- */
-std::string generateXmpForPrimaryImage(int secondary_image_length,
-                                       ultrahdr_metadata_struct& metadata);
-
-/*
- * This method generates XMP metadata for the recovery map image.
- *
- * below is an example of the XMP metadata that this function generates where
- * max_content_boost = 8.0
- * min_content_boost = 0.5
- *
- * <x:xmpmeta
- *   xmlns:x="adobe:ns:meta/"
- *   x:xmptk="Adobe XMP Core 5.1.2">
- *   <rdf:RDF
- *     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
- *     <rdf:Description
- *       xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
- *       hdrgm:Version="1"
- *       hdrgm:GainMapMin="-1"
- *       hdrgm:GainMapMax="3"
- *       hdrgm:Gamma="1"
- *       hdrgm:OffsetSDR="0"
- *       hdrgm:OffsetHDR="0"
- *       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(ultrahdr_metadata_struct& metadata);
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_JPEGRUTILS_H
diff --git a/libs/ultrahdr/include/ultrahdr/multipictureformat.h b/libs/ultrahdr/include/ultrahdr/multipictureformat.h
deleted file mode 100644
index c5bd09d..0000000
--- a/libs/ultrahdr/include/ultrahdr/multipictureformat.h
+++ /dev/null
@@ -1,64 +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_ULTRAHDR_MULTIPICTUREFORMAT_H
-#define ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
-
-#include <ultrahdr/jpegrutils.h>
-
-#ifdef USE_BIG_ENDIAN
-#undef USE_BIG_ENDIAN
-#define USE_BIG_ENDIAN true
-#endif
-
-namespace android::ultrahdr {
-
-constexpr size_t kNumPictures = 2;
-constexpr size_t kMpEndianSize = 4;
-constexpr uint16_t kTagSerializedCount = 3;
-constexpr uint32_t kTagSize = 12;
-
-constexpr uint16_t kTypeLong = 0x4;
-constexpr uint16_t kTypeUndefined = 0x7;
-
-static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'};
-constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00};
-constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A};
-
-constexpr uint16_t kVersionTag = 0xB000;
-constexpr uint16_t kVersionType = kTypeUndefined;
-constexpr uint32_t kVersionCount = 4;
-constexpr size_t kVersionSize = 4;
-constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'};
-
-constexpr uint16_t kNumberOfImagesTag = 0xB001;
-constexpr uint16_t kNumberOfImagesType = kTypeLong;
-constexpr uint32_t kNumberOfImagesCount = 1;
-
-constexpr uint16_t kMPEntryTag = 0xB002;
-constexpr uint16_t kMPEntryType = kTypeUndefined;
-constexpr uint32_t kMPEntrySize = 16;
-
-constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000;
-constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000;
-
-size_t calculateMpfSize();
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-                           int secondary_image_size, int secondary_image_offset);
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_MULTIPICTUREFORMAT_H
diff --git a/libs/ultrahdr/include/ultrahdr/ultrahdr.h b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
deleted file mode 100644
index 0252391..0000000
--- a/libs/ultrahdr/include/ultrahdr/ultrahdr.h
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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
-
-#include <string>
-
-namespace android::ultrahdr {
-// Color gamuts for image data
-typedef enum {
-  ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1,
-  ULTRAHDR_COLORGAMUT_BT709,
-  ULTRAHDR_COLORGAMUT_P3,
-  ULTRAHDR_COLORGAMUT_BT2100,
-  ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100,
-} ultrahdr_color_gamut;
-
-// Transfer functions for image data
-// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
-typedef enum {
-  ULTRAHDR_TF_UNSPECIFIED = -1,
-  ULTRAHDR_TF_LINEAR = 0,
-  ULTRAHDR_TF_HLG = 1,
-  ULTRAHDR_TF_PQ = 2,
-  ULTRAHDR_TF_SRGB = 3,
-  ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB,
-} ultrahdr_transfer_function;
-
-// Target output formats for decoder
-typedef enum {
-  ULTRAHDR_OUTPUT_UNSPECIFIED = -1,
-  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_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
-} ultrahdr_output_format;
-
-/*
- * Holds information for gain map related metadata.
- *
- * Not: all values stored in linear. This differs from the metadata encoding in XMP, where
- * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and
- * hdrCapacityMax are stored in log2 space.
- */
-struct ultrahdr_metadata_struct {
-  // Ultra HDR format version
-  std::string version;
-  // Max Content Boost for the map
-  float maxContentBoost;
-  // Min Content Boost for the map
-  float minContentBoost;
-  // Gamma of the map data
-  float gamma;
-  // Offset for SDR data in map calculations
-  float offsetSdr;
-  // Offset for HDR data in map calculations
-  float offsetHdr;
-  // HDR capacity to apply the map at all
-  float hdrCapacityMin;
-  // HDR capacity to apply the map completely
-  float hdrCapacityMax;
-};
-typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
-
-}  // namespace android::ultrahdr
-
-#endif //ANDROID_ULTRAHDR_ULTRAHDR_H
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
deleted file mode 100644
index 2e7940c..0000000
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ /dev/null
@@ -1,543 +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 <ultrahdr/jpegdecoderhelper.h>
-
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <setjmp.h>
-#include <string>
-
-using namespace std;
-
-namespace android::ultrahdr {
-
-#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
-
-const uint32_t kAPP0Marker = JPEG_APP0;     // JFIF
-const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
-const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
-
-constexpr uint32_t kICCMarkerHeaderSize = 14;
-constexpr uint8_t kICCSig[] = {
-        'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
-};
-constexpr uint8_t kXmpNameSpace[] = {
-        'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
-        '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
-};
-constexpr uint8_t kExifIdCode[] = {
-        'E', 'x', 'i', 'f', '\0', '\0',
-};
-
-struct jpegr_source_mgr : jpeg_source_mgr {
-    jpegr_source_mgr(const uint8_t* ptr, int len);
-    ~jpegr_source_mgr();
-
-    const uint8_t* mBufferPtr;
-    size_t mBufferLength;
-};
-
-struct jpegrerror_mgr {
-    struct jpeg_error_mgr pub;
-    jmp_buf setjmp_buffer;
-};
-
-static void jpegr_init_source(j_decompress_ptr cinfo) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-    src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr);
-    src->bytes_in_buffer = src->mBufferLength;
-}
-
-static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) {
-    ALOGE("%s : should not get here", __func__);
-    return FALSE;
-}
-
-static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) {
-    jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
-
-    if (num_bytes > static_cast<long>(src->bytes_in_buffer)) {
-        ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer");
-    } else {
-        src->next_input_byte += num_bytes;
-        src->bytes_in_buffer -= num_bytes;
-    }
-}
-
-static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {}
-
-jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len)
-      : mBufferPtr(ptr), mBufferLength(len) {
-    init_source = jpegr_init_source;
-    fill_input_buffer = jpegr_fill_input_buffer;
-    skip_input_data = jpegr_skip_input_data;
-    resync_to_restart = jpeg_resync_to_restart;
-    term_source = jpegr_term_source;
-}
-
-jpegr_source_mgr::~jpegr_source_mgr() {}
-
-static void jpegrerror_exit(j_common_ptr cinfo) {
-    jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err);
-    longjmp(err->setjmp_buffer, 1);
-}
-
-JpegDecoderHelper::JpegDecoderHelper() {}
-
-JpegDecoderHelper::~JpegDecoderHelper() {}
-
-bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
-    if (image == nullptr || length <= 0) {
-        ALOGE("Image size can not be handled: %d", length);
-        return false;
-    }
-    mResultBuffer.clear();
-    mXMPBuffer.clear();
-    return decode(image, length, decodeToRGBA);
-}
-
-void* JpegDecoderHelper::getDecompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void* JpegDecoderHelper::getXMPPtr() {
-    return mXMPBuffer.data();
-}
-
-size_t JpegDecoderHelper::getXMPSize() {
-    return mXMPBuffer.size();
-}
-
-void* JpegDecoderHelper::getEXIFPtr() {
-    return mEXIFBuffer.data();
-}
-
-size_t JpegDecoderHelper::getEXIFSize() {
-    return mEXIFBuffer.size();
-}
-
-void* JpegDecoderHelper::getICCPtr() {
-    return mICCBuffer.data();
-}
-
-size_t JpegDecoderHelper::getICCSize() {
-    return mICCBuffer.size();
-}
-
-size_t JpegDecoderHelper::getDecompressedImageWidth() {
-    return mWidth;
-}
-
-size_t JpegDecoderHelper::getDecompressedImageHeight() {
-    return mHeight;
-}
-
-// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
-// in the image file.
-// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
-// two bytes of package length which is stored in marker->original_length, and the real data
-// which is stored in marker->data.
-bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
-    jpeg_decompress_struct cinfo;
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    jpegrerror_mgr myerr;
-
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-
-    cinfo.src = &mgr;
-    jpeg_read_header(&cinfo, TRUE);
-
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker;
-         marker = marker->next) {
-
-        pos += 4;
-        pos += marker->original_length;
-
-        if (marker->marker != kAPP1Marker) {
-            continue;
-        }
-
-        const unsigned int len = marker->data_length;
-
-        if (len > sizeof(kExifIdCode) &&
-            !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            mExifPos = pos - marker->original_length;
-            break;
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
-    bool status = true;
-    jpeg_decompress_struct cinfo;
-    jpegrerror_mgr myerr;
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    // Save XMP data, EXIF data, and ICC data.
-    // Here we only handle the first XMP / EXIF / ICC package.
-    // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
-    // two bytes of package length which is stored in marker->original_length, and the real data
-    // which is stored in marker->data.
-    bool exifAppears = false;
-    bool xmpAppears = false;
-    bool iccAppears = false;
-    size_t pos = 2;  // position after SOI
-    for (jpeg_marker_struct* marker = cinfo.marker_list;
-         marker && !(exifAppears && xmpAppears && iccAppears);
-         marker = marker->next) {
-         pos += 4;
-         pos += marker->original_length;
-        if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
-            continue;
-        }
-        const unsigned int len = marker->data_length;
-        if (!xmpAppears &&
-            len > sizeof(kXmpNameSpace) &&
-            !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
-            mXMPBuffer.resize(len+1, 0);
-            memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
-            xmpAppears = true;
-        } else if (!exifAppears &&
-                   len > sizeof(kExifIdCode) &&
-                   !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-            mEXIFBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
-            exifAppears = true;
-            mExifPos = pos - marker->original_length;
-        } else if (!iccAppears &&
-                   len > sizeof(kICCSig) &&
-                   !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
-            mICCBuffer.resize(len, 0);
-            memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
-            iccAppears = true;
-        }
-    }
-
-    mWidth = cinfo.image_width;
-    mHeight = cinfo.image_height;
-    if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
-        status = false;
-        goto CleanUp;
-    }
-
-    if (decodeToRGBA) {
-        // The primary image is expected to be yuv420 sampling
-        if (cinfo.jpeg_color_space != JCS_YCbCr) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
-            goto CleanUp;
-        }
-        if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-            cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-            cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-            status = false;
-            ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
-            goto CleanUp;
-        }
-        // 4 bytes per pixel
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
-        cinfo.out_color_space = JCS_EXT_RGBA;
-    } else {
-        if (cinfo.jpeg_color_space == JCS_YCbCr) {
-            if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
-                cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
-                cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
-                status = false;
-                ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
-                goto CleanUp;
-            }
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
-        } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-            mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
-        } else {
-            status = false;
-            ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
-            goto CleanUp;
-        }
-        cinfo.out_color_space = cinfo.jpeg_color_space;
-        cinfo.raw_data_out = TRUE;
-    }
-
-    cinfo.dct_method = JDCT_ISLOW;
-    jpeg_start_decompress(&cinfo);
-    if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
-                    cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
-        status = false;
-        goto CleanUp;
-    }
-
-CleanUp:
-    jpeg_finish_decompress(&cinfo);
-    jpeg_destroy_decompress(&cinfo);
-
-    return status;
-}
-
-bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
-                                   bool isSingleChannel) {
-    return isSingleChannel
-            ? decompressSingleChannel(cinfo, dest)
-            : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
-                                                        : decompressYUV(cinfo, dest));
-}
-
-bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
-                                                     size_t* pHeight, std::vector<uint8_t>* iccData,
-                                                     std::vector<uint8_t>* exifData) {
-    jpeg_decompress_struct cinfo;
-    jpegrerror_mgr myerr;
-    cinfo.err = jpeg_std_error(&myerr.pub);
-    myerr.pub.error_exit = jpegrerror_exit;
-    if (setjmp(myerr.setjmp_buffer)) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-    jpeg_create_decompress(&cinfo);
-
-    jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
-    jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
-
-    jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
-    cinfo.src = &mgr;
-    if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
-        jpeg_destroy_decompress(&cinfo);
-        return false;
-    }
-
-    if (pWidth != nullptr) {
-        *pWidth = cinfo.image_width;
-    }
-    if (pHeight != nullptr) {
-        *pHeight = cinfo.image_height;
-    }
-
-    if (iccData != nullptr) {
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
-            if (marker->marker != kAPP2Marker) {
-                continue;
-            }
-            if (marker->data_length <= kICCMarkerHeaderSize ||
-                memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
-                continue;
-            }
-
-            iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length);
-        }
-    }
-
-    if (exifData != nullptr) {
-        bool exifAppears = false;
-        for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
-             marker = marker->next) {
-            if (marker->marker != kAPP1Marker) {
-                continue;
-            }
-
-            const unsigned int len = marker->data_length;
-            if (len >= sizeof(kExifIdCode) &&
-                !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
-                exifData->resize(len, 0);
-                memcpy(static_cast<void*>(exifData->data()), marker->data, len);
-                exifAppears = true;
-            }
-        }
-    }
-
-    jpeg_destroy_decompress(&cinfo);
-    return true;
-}
-
-bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPLE* out = (JSAMPLE*)dest;
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
-        out += cinfo->image_width * 4;
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = cinfo->image_width * cinfo->image_height;
-    size_t uv_plane_size = y_plane_size / 4;
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
-    uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->output_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * (cinfo->image_width / 2);
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-            for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-                memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2);
-                memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2);
-            }
-        }
-    }
-    return true;
-}
-
-bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo,
-                                                const uint8_t* dest) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(dest);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    bool is_width_aligned = (aligned_width == cinfo->image_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[1]{y_intrm};
-    if (!is_width_aligned) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-        }
-    }
-
-    while (cinfo->output_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->output_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * cinfo->image_width;
-            } else {
-                y[i] = empty.get();
-            }
-        }
-
-        int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
-                                           kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-        if (!is_width_aligned) {
-            for (int i = 0; i < kCompressBatchSize; ++i) {
-                memcpy(y[i], y_intrm[i], cinfo->image_width);
-            }
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
deleted file mode 100644
index 13ae742..0000000
--- a/libs/ultrahdr/jpegencoderhelper.cpp
+++ /dev/null
@@ -1,273 +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 <cstring>
-#include <memory>
-#include <vector>
-
-#include <ultrahdr/jpegencoderhelper.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
-struct destination_mgr {
-    struct jpeg_destination_mgr mgr;
-    JpegEncoderHelper* encoder;
-};
-
-JpegEncoderHelper::JpegEncoderHelper() {}
-
-JpegEncoderHelper::~JpegEncoderHelper() {}
-
-bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                                      int height, int lumaStride, int chromaStride, int quality,
-                                      const void* iccBuffer, unsigned int iccSize) {
-    mResultBuffer.clear();
-    if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
-                iccSize)) {
-        return false;
-    }
-    ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
-          mResultBuffer.size());
-    return true;
-}
-
-void* JpegEncoderHelper::getCompressedImagePtr() {
-    return mResultBuffer.data();
-}
-
-size_t JpegEncoderHelper::getCompressedImageSize() {
-    return mResultBuffer.size();
-}
-
-void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(kBlockSize);
-    dest->mgr.next_output_byte = &buffer[0];
-    dest->mgr.free_in_buffer = buffer.size();
-}
-
-boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    size_t oldsize = buffer.size();
-    buffer.resize(oldsize + kBlockSize);
-    dest->mgr.next_output_byte = &buffer[oldsize];
-    dest->mgr.free_in_buffer = kBlockSize;
-    return true;
-}
-
-void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
-    destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
-    std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
-    buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
-}
-
-void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
-    char buffer[JMSG_LENGTH_MAX];
-
-    /* Create the message */
-    (*cinfo->err->format_message)(cinfo, buffer);
-    ALOGE("%s\n", buffer);
-}
-
-bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
-                               int height, int lumaStride, int chromaStride, int quality,
-                               const void* iccBuffer, unsigned int iccSize) {
-    jpeg_compress_struct cinfo;
-    jpeg_error_mgr jerr;
-
-    cinfo.err = jpeg_std_error(&jerr);
-    cinfo.err->output_message = &outputErrorMessage;
-    jpeg_create_compress(&cinfo);
-    setJpegDestination(&cinfo);
-    setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
-    jpeg_start_compress(&cinfo, TRUE);
-    if (iccBuffer != nullptr && iccSize > 0) {
-        jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
-    }
-    bool status = cinfo.num_components == 1
-            ? compressY(&cinfo, yBuffer, lumaStride)
-            : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
-    jpeg_finish_compress(&cinfo);
-    jpeg_destroy_compress(&cinfo);
-
-    return status;
-}
-
-void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
-    destination_mgr* dest = static_cast<struct destination_mgr*>(
-            (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT,
-                                       sizeof(destination_mgr)));
-    dest->encoder = this;
-    dest->mgr.init_destination = &initDestination;
-    dest->mgr.empty_output_buffer = &emptyOutputBuffer;
-    dest->mgr.term_destination = &terminateDestination;
-    cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
-}
-
-void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
-                                              jpeg_compress_struct* cinfo, bool isSingleChannel) {
-    cinfo->image_width = width;
-    cinfo->image_height = height;
-    cinfo->input_components = isSingleChannel ? 1 : 3;
-    cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
-    jpeg_set_defaults(cinfo);
-    jpeg_set_quality(cinfo, quality, TRUE);
-    cinfo->raw_data_in = TRUE;
-    cinfo->dct_method = JDCT_ISLOW;
-    cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
-    for (int i = 1; i < cinfo->num_components; i++) {
-        cinfo->comp_info[i].h_samp_factor = 1;
-        cinfo->comp_info[i].v_samp_factor = 1;
-    }
-}
-
-bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                    const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPROW cb[kCompressBatchSize / 2];
-    JSAMPROW cr[kCompressBatchSize / 2];
-    JSAMPARRAY planes[3]{y, cb, cr};
-
-    size_t y_plane_size = lumaStride * cinfo->image_height;
-    size_t u_plane_size = chromaStride * cinfo->image_height / 2;
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
-    uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    uint8_t* v_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPROW cb_intrm[kCompressBatchSize / 2];
-    JSAMPROW cr_intrm[kCompressBatchSize / 2];
-    JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
-        v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            int offset_intrm = i * (aligned_width / 2);
-            cb_intrm[i] = u_plane_intrm + offset_intrm;
-            cr_intrm[i] = v_plane_intrm + offset_intrm;
-            memset(cb_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-            memset(cr_intrm[i] + cinfo->image_width / 2, 0,
-                   (aligned_width - cinfo->image_width) / 2);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        // cb, cr only have half scanlines
-        for (int i = 0; i < kCompressBatchSize / 2; ++i) {
-            size_t scanline = cinfo->next_scanline / 2 + i;
-            if (scanline < cinfo->image_height / 2) {
-                int offset = scanline * chromaStride;
-                cb[i] = u_plane + offset;
-                cr[i] = v_plane + offset;
-            } else {
-                cb[i] = cr[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
-                memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
-                                  int lumaStride) {
-    JSAMPROW y[kCompressBatchSize];
-    JSAMPARRAY planes[1]{y};
-
-    uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
-    std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
-    memset(empty.get(), 0, cinfo->image_width);
-
-    const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
-    const bool need_padding = (lumaStride < aligned_width);
-    std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
-    uint8_t* y_plane_intrm = nullptr;
-    uint8_t* u_plane_intrm = nullptr;
-    JSAMPROW y_intrm[kCompressBatchSize];
-    JSAMPARRAY planes_intrm[]{y_intrm};
-    if (need_padding) {
-        size_t mcu_row_size = aligned_width * kCompressBatchSize;
-        buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
-        y_plane_intrm = buffer_intrm.get();
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            y_intrm[i] = y_plane_intrm + i * aligned_width;
-            memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
-        }
-    }
-
-    while (cinfo->next_scanline < cinfo->image_height) {
-        for (int i = 0; i < kCompressBatchSize; ++i) {
-            size_t scanline = cinfo->next_scanline + i;
-            if (scanline < cinfo->image_height) {
-                y[i] = y_plane + scanline * lumaStride;
-            } else {
-                y[i] = empty.get();
-            }
-            if (need_padding) {
-                memcpy(y_intrm[i], y[i], cinfo->image_width);
-            }
-        }
-        int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
-                                            kCompressBatchSize);
-        if (processed != kCompressBatchSize / 2) {
-            ALOGE("Number of processed lines does not equal input lines.");
-            return false;
-        }
-    }
-    return true;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
deleted file mode 100644
index 3d70fce..0000000
--- a/libs/ultrahdr/jpegr.cpp
+++ /dev/null
@@ -1,1503 +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 <cmath>
-#include <condition_variable>
-#include <deque>
-#include <memory>
-#include <mutex>
-#include <thread>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-#include <ultrahdr/multipictureformat.h>
-
-#include <image_io/base/data_segment_data_source.h>
-#include <image_io/jpeg/jpeg_info.h>
-#include <image_io/jpeg/jpeg_info_builder.h>
-#include <image_io/jpeg/jpeg_marker.h>
-#include <image_io/jpeg/jpeg_scanner.h>
-
-#include <utils/Log.h>
-
-using namespace std;
-using namespace photos_editing_formats::image_io;
-
-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_GAIN_LUT 1
-
-#define JPEGR_CHECK(x)          \
-  {                             \
-    status_t status = (x);      \
-    if ((status) != NO_ERROR) { \
-      return status;            \
-    }                           \
-  }
-
-// JPEG compress quality (0 ~ 100) for gain map
-static const int kMapCompressQuality = 85;
-
-#define CONFIG_MULTITHREAD 1
-int GetCPUCoreCount() {
-  int cpuCoreCount = 1;
-#if CONFIG_MULTITHREAD
-#if defined(_SC_NPROCESSORS_ONLN)
-  cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
-#else
-  // _SC_NPROC_ONLN must be defined...
-  cpuCoreCount = sysconf(_SC_NPROC_ONLN);
-#endif
-#endif
-  return cpuCoreCount;
-}
-
-/*
- * Helper function copies the JPEG image from without EXIF.
- *
- * @param pDest destination of the data to be written.
- * @param pSource source of data being written.
- * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
- *                 (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
- * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
- */
-static void copyJpegWithoutExif(jr_compressed_ptr pDest,
-                                jr_compressed_ptr pSource,
-                                size_t exif_pos,
-                                size_t exif_size) {
-  const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
-  pDest->length = pSource->length - exif_size - exif_offset;
-  pDest->data = new uint8_t[pDest->length];
-  pDest->maxLength = pDest->length;
-  pDest->colorGamut = pSource->colorGamut;
-  memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
-  memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
-         (uint8_t*)pSource->data + exif_pos + exif_size,
-         pSource->length - exif_pos - exif_size);
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr) {
-  if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for input p010 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
-    ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
-    ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
-          kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
-    ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
-          kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->luma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->chroma_data != nullptr &&
-      p010_image_ptr->chroma_stride < p010_image_ptr->width) {
-    ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
-          p010_image_ptr->chroma_stride, p010_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
-    ALOGE("Received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
-    ALOGE("Invalid hdr transfer function %d", hdr_tf);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr == nullptr) {
-    return NO_ERROR;
-  }
-  if (yuv420_image_ptr->data == nullptr) {
-    ALOGE("Received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->luma_stride != 0 &&
-      yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
-    ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
-          yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (yuv420_image_ptr->chroma_data != nullptr &&
-      yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
-    ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
-          yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (p010_image_ptr->width != yuv420_image_ptr->width ||
-      p010_image_ptr->height != yuv420_image_ptr->height) {
-    ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
-          p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
-    ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return NO_ERROR;
-}
-
-status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
-                                       jr_uncompressed_ptr yuv420_image_ptr,
-                                       ultrahdr_transfer_function hdr_tf,
-                                       jr_compressed_ptr dest_ptr, int quality) {
-  if (quality < 0 || quality > 100) {
-    ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
-}
-
-/* Encode API-0 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
-  unique_ptr<uint8_t[]> yuv420_image_data =
-          make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
-  jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
-                                            .width = p010_image.width,
-                                            .height = p010_image.height,
-                                            .colorGamut = p010_image.colorGamut,
-                                            .luma_stride = yu420_luma_stride,
-                                            .chroma_data = nullptr,
-                                            .chroma_stride = yu420_luma_stride >> 1};
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-
-  // tone map
-  JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  // convert to Bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
-                                         yuv420_image.width, yuv420_image.height,
-                                         yuv420_image.luma_stride, yuv420_image.chroma_stride,
-                                         quality, icc->getData(), icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-
-  return NO_ERROR;
-}
-
-/* Encode API-1 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
-                            jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr for exif metadata");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                            .length = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .maxLength = static_cast<int>(
-                                                    jpeg_enc_obj_gm.getCompressedImageSize()),
-                                            .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
-
-  jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
-  unique_ptr<uint8_t[]> yuv_420_bt601_data;
-  // Convert to bt601 YUV encoding for JPEG encode
-  if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
-    const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
-    yuv_420_bt601_data =
-            make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
-    yuv420_bt601_image.data = yuv_420_bt601_data.get();
-    yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
-    yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-    yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
-    yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
-
-    {
-      // copy luma
-      uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
-      uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
-      if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
-        memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
-      } else {
-        for (size_t i = 0; i < yuv420_image.height; i++) {
-          memcpy(y_dst, y_src, yuv420_image.width);
-          if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
-            memset(y_dst + yuv420_image.width, 0,
-                   yuv420_bt601_image.luma_stride - yuv420_image.width);
-          }
-          y_dst += yuv420_bt601_image.luma_stride;
-          y_src += yuv420_image.luma_stride;
-        }
-      }
-    }
-
-    if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
-      // copy luma
-      uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
-    } else {
-      // copy cb & cr
-      uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
-      uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
-      uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
-      uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
-      for (size_t i = 0; i < yuv420_image.height / 2; i++) {
-        memcpy(cb_dst, cb_src, yuv420_image.width / 2);
-        memcpy(cr_dst, cr_src, yuv420_image.width / 2);
-        if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
-          memset(cb_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-          memset(cr_dst + yuv420_image.width / 2, 0,
-                 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
-        }
-        cb_dst += yuv420_bt601_image.chroma_stride;
-        cb_src += yuv420_image.chroma_stride;
-        cr_dst += yuv420_bt601_image.chroma_stride;
-        cr_src += yuv420_image.chroma_stride;
-      }
-    }
-    JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
-  }
-
-  // compress 420 image
-  JpegEncoderHelper jpeg_enc_obj_yuv420;
-  if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
-                                         reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
-                                         yuv420_bt601_image.width, yuv420_bt601_image.height,
-                                         yuv420_bt601_image.luma_stride,
-                                         yuv420_bt601_image.chroma_stride, quality, icc->getData(),
-                                         icc->getLength())) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
-                                  .length = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .maxLength = static_cast<int>(
-                                          jpeg_enc_obj_yuv420.getCompressedImageSize()),
-                                  .colorGamut = yuv420_image.colorGamut};
-
-  // append gain map, no ICC since JPEG encode already did it
-  JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
-                            &metadata, dest));
-  return NO_ERROR;
-}
-
-/* Encode API-2 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_uncompressed_ptr yuv420_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420_image_ptr == nullptr) {
-    ALOGE("received nullptr for uncompressed 420 image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
-      ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-  jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-3 */
-status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
-                            jr_compressed_ptr yuv420jpg_image_ptr,
-                            ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
-  // validate input arguments
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
-    return ret;
-  }
-
-  // clean up input structure for later usage
-  jpegr_uncompressed_struct p010_image = *p010_image_ptr;
-  if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
-  if (!p010_image.chroma_data) {
-    uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
-    p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
-    p010_image.chroma_stride = p010_image.luma_stride;
-  }
-
-  // decode input jpeg, gamut is going to be bt601.
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
-                                           yuv420jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_uncompressed_struct yuv420_image{};
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
-  if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
-  if (!yuv420_image.chroma_data) {
-    uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-    yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
-    yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
-  }
-
-  if (p010_image_ptr->width != yuv420_image.width ||
-      p010_image_ptr->height != yuv420_image.height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-
-  // gain map
-  ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
-  jpegr_uncompressed_struct gainmap_image;
-  JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
-                              true /* sdr_is_601 */));
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
-
-  // compress gain map
-  JpegEncoderHelper jpeg_enc_obj_gm;
-  JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
-  jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
-                                              .length = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .maxLength = static_cast<int>(
-                                                      jpeg_enc_obj_gm.getCompressedImageSize()),
-                                              .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
-}
-
-/* Encode API-4 */
-status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
-                            jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
-                            jr_compressed_ptr dest) {
-  if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpeg image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed gain map");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for destination");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // We just want to check if ICC is present, so don't do a full decode. Note,
-  // this doesn't verify that the ICC is valid.
-  JpegDecoderHelper decoder;
-  std::vector<uint8_t> icc;
-  decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
-                                       /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
-                                       /* exifData */ nullptr);
-
-  // Add ICC if not already present.
-  if (icc.size() > 0) {
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              /* icc */ nullptr, /* icc size */ 0, metadata, dest));
-  } else {
-    sp<DataStruct> newIcc =
-            IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
-    JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
-                              newIcc->getData(), newIcc->getLength(), metadata, dest));
-  }
-
-  return NO_ERROR;
-}
-
-status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (jpeg_image_info_ptr == nullptr) {
-    ALOGE("received nullptr for compressed jpegr info struct");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  jpegr_compressed_struct primary_image, gainmap_image;
-  status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
-  if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-    return status;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_hdr;
-  if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
-                                                     &jpeg_image_info_ptr->width,
-                                                     &jpeg_image_info_ptr->height,
-                                                     jpeg_image_info_ptr->iccData,
-                                                     jpeg_image_info_ptr->exifData)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  return status;
-}
-
-/* Decode API */
-status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
-                            float max_display_boost, jr_exif_ptr exif,
-                            ultrahdr_output_format output_format,
-                            jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
-  if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
-    ALOGE("received nullptr for compressed jpegr image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (dest == nullptr || dest->data == nullptr) {
-    ALOGE("received nullptr for dest image");
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (max_display_boost < 1.0f) {
-    ALOGE("received bad value for max_display_boost %f", max_display_boost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (exif != nullptr && exif->data == nullptr) {
-    ALOGE("received nullptr address for exif data");
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
-    ALOGE("received bad value for output format %d", output_format);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
-  status_t status =
-          extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
-  if (status != NO_ERROR) {
-    if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
-      ALOGE("received invalid compressed jpegr image");
-      return status;
-    }
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_yuv420;
-  if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
-                                           (output_format == ULTRAHDR_OUTPUT_SDR))) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  } else {
-    if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
-         jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
-        jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
-      return ERROR_JPEGR_CALCULATION_ERROR;
-    }
-  }
-
-  if (exif != nullptr) {
-    if (exif->data == nullptr) {
-      return ERROR_JPEGR_INVALID_NULL_PTR;
-    }
-    if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
-      return ERROR_JPEGR_BUFFER_TOO_SMALL;
-    }
-    memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
-    exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
-  }
-
-  if (output_format == ULTRAHDR_OUTPUT_SDR) {
-    dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-    dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-    memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
-           dest->width * dest->height * 4);
-    return NO_ERROR;
-  }
-
-  JpegDecoderHelper jpeg_dec_obj_gm;
-  if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
-      jpeg_dec_obj_gm.getDecompressedImageSize()) {
-    return ERROR_JPEGR_CALCULATION_ERROR;
-  }
-
-  jpegr_uncompressed_struct gainmap_image;
-  gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
-  gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
-  gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
-
-  if (gainmap_image_ptr != nullptr) {
-    gainmap_image_ptr->width = gainmap_image.width;
-    gainmap_image_ptr->height = gainmap_image.height;
-    int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
-    gainmap_image_ptr->data = malloc(size);
-    memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
-  }
-
-  ultrahdr_metadata_struct uhdr_metadata;
-  if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
-                          jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
-    return ERROR_JPEGR_INVALID_METADATA;
-  }
-
-  if (metadata != nullptr) {
-    metadata->version = uhdr_metadata.version;
-    metadata->minContentBoost = uhdr_metadata.minContentBoost;
-    metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
-    metadata->gamma = uhdr_metadata.gamma;
-    metadata->offsetSdr = uhdr_metadata.offsetSdr;
-    metadata->offsetHdr = uhdr_metadata.offsetHdr;
-    metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
-    metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
-  }
-
-  jpegr_uncompressed_struct yuv420_image;
-  yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
-  yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
-  yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
-  yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
-                                                         jpeg_dec_obj_yuv420.getICCSize());
-  yuv420_image.luma_stride = yuv420_image.width;
-  uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
-  yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
-  yuv420_image.chroma_stride = yuv420_image.width >> 1;
-
-  JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
-                           max_display_boost, dest));
-  return NO_ERROR;
-}
-
-status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
-                                JpegEncoderHelper* jpeg_enc_obj_ptr) {
-  if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  // Don't need to convert YUV to Bt601 since single channel
-  if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
-                                       gainmap_image_ptr->width, gainmap_image_ptr->height,
-                                       gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
-                                       nullptr, 0)) {
-    return ERROR_JPEGR_ENCODE_ERROR;
-  }
-
-  return NO_ERROR;
-}
-
-const int kJobSzInRows = 16;
-static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
-              "align job size to kMapDimensionScaleFactor");
-
-class JobQueue {
-public:
-  bool dequeueJob(size_t& rowStart, size_t& rowEnd);
-  void enqueueJob(size_t rowStart, size_t rowEnd);
-  void markQueueForEnd();
-  void reset();
-
-private:
-  bool mQueuedAllJobs = false;
-  std::deque<std::tuple<size_t, size_t>> mJobs;
-  std::mutex mMutex;
-  std::condition_variable mCv;
-};
-
-bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  while (true) {
-    if (mJobs.empty()) {
-      if (mQueuedAllJobs) {
-        return false;
-      } else {
-        mCv.wait_for(lock, std::chrono::milliseconds(100));
-      }
-    } else {
-      auto it = mJobs.begin();
-      rowStart = std::get<0>(*it);
-      rowEnd = std::get<1>(*it);
-      mJobs.erase(it);
-      return true;
-    }
-  }
-  return false;
-}
-
-void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.push_back(std::make_tuple(rowStart, rowEnd));
-  lock.unlock();
-  mCv.notify_one();
-}
-
-void JobQueue::markQueueForEnd() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mQueuedAllJobs = true;
-  lock.unlock();
-  mCv.notify_all();
-}
-
-void JobQueue::reset() {
-  std::unique_lock<std::mutex> lock{mMutex};
-  mJobs.clear();
-  mQueuedAllJobs = false;
-}
-
-status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                                jr_uncompressed_ptr p010_image_ptr,
-                                ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
-                                jr_uncompressed_ptr dest, bool sdr_is_601) {
-  if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
-      p010_image_ptr->chroma_data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (yuv420_image_ptr->width != p010_image_ptr->width ||
-      yuv420_image_ptr->height != p010_image_ptr->height) {
-    return ERROR_JPEGR_RESOLUTION_MISMATCH;
-  }
-  if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-
-  dest->data = new uint8_t[map_width * map_height];
-  dest->width = map_width;
-  dest->height = map_height;
-  dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  dest->luma_stride = map_width;
-  dest->chroma_data = nullptr;
-  dest->chroma_stride = 0;
-  std::unique_ptr<uint8_t[]> map_data;
-  map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
-
-  ColorTransformFn hdrInvOetf = nullptr;
-  float hdr_white_nits;
-  switch (hdr_tf) {
-    case ULTRAHDR_TF_LINEAR:
-      hdrInvOetf = identityConversion;
-      // Note: this will produce clipping if the input exceeds kHlgMaxNits.
-      // TODO: TF LINEAR will be deprecated.
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_HLG:
-#if USE_HLG_INVOETF_LUT
-      hdrInvOetf = hlgInvOetfLUT;
-#else
-      hdrInvOetf = hlgInvOetf;
-#endif
-      hdr_white_nits = kHlgMaxNits;
-      break;
-    case ULTRAHDR_TF_PQ:
-#if USE_PQ_INVOETF_LUT
-      hdrInvOetf = pqInvOetfLUT;
-#else
-      hdrInvOetf = pqInvOetf;
-#endif
-      hdr_white_nits = kPqMaxNits;
-      break;
-    default:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_TRANS_FUNC;
-  }
-
-  metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
-  metadata->minContentBoost = 1.0f;
-  metadata->gamma = 1.0f;
-  metadata->offsetSdr = 0.0f;
-  metadata->offsetHdr = 0.0f;
-  metadata->hdrCapacityMin = 1.0f;
-  metadata->hdrCapacityMax = metadata->maxContentBoost;
-
-  float log2MinBoost = log2(metadata->minContentBoost);
-  float log2MaxBoost = log2(metadata->maxContentBoost);
-
-  ColorTransformFn hdrGamutConversionFn =
-          getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
-
-  ColorCalculationFn luminanceFn = nullptr;
-  ColorTransformFn sdrYuvToRgbFn = nullptr;
-  switch (yuv420_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      luminanceFn = srgbLuminance;
-      sdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      luminanceFn = p3Luminance;
-      sdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      luminanceFn = bt2100Luminance;
-      sdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-  if (sdr_is_601) {
-    sdrYuvToRgbFn = p3YuvToRgb;
-  }
-
-  ColorTransformFn hdrYuvToRgbFn = nullptr;
-  switch (p010_image_ptr->colorGamut) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      hdrYuvToRgbFn = srgbYuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      hdrYuvToRgbFn = p3YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      hdrYuvToRgbFn = bt2100YuvToRgb;
-      break;
-    case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
-      // Should be impossible to hit after input validation.
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  std::mutex mutex;
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
-  JobQueue jobQueue;
-
-  std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
-                                       hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
-                                       hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
-                                       &jobQueue]() -> void {
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < dest->width; ++x) {
-          Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
-          // We are assuming the SDR input is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
-#else
-          Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
-#endif
-          float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
-
-          Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
-          Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
-          Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
-          hdr_rgb = hdrGamutConversionFn(hdr_rgb);
-          float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
-
-          size_t pixel_idx = x + y * dest->width;
-          reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-                  encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
-        }
-      }
-    }
-  };
-
-  // generate map
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(generateMap));
-  }
-
-  rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
-  for (size_t rowStart = 0; rowStart < map_height;) {
-    size_t rowEnd = std::min(rowStart + rowStep, map_height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  generateMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-
-  map_data.release();
-  return NO_ERROR;
-}
-
-status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
-                             jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
-                             ultrahdr_output_format output_format, float max_display_boost,
-                             jr_uncompressed_ptr dest) {
-  if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr || yuv420_image_ptr->data == nullptr ||
-      yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare(kJpegrVersion)) {
-    ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->gamma != 1.0f) {
-    ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
-    ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-  if (metadata->hdrCapacityMin != metadata->minContentBoost ||
-      metadata->hdrCapacityMax != metadata->maxContentBoost) {
-    ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_UNSUPPORTED_METADATA;
-  }
-
-  // TODO: remove once map scaling factor is computed based on actual map dims
-  size_t image_width = yuv420_image_ptr->width;
-  size_t image_height = yuv420_image_ptr->height;
-  size_t map_width = image_width / kMapDimensionScaleFactor;
-  size_t map_height = image_height / kMapDimensionScaleFactor;
-  if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
-    ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
-          "resolution is %dx%d, received gain map resolution is %dx%d",
-          (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  dest->width = yuv420_image_ptr->width;
-  dest->height = yuv420_image_ptr->height;
-  ShepardsIDW idwTable(kMapDimensionScaleFactor);
-  float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
-  GainLUT gainLUT(metadata, display_boost);
-
-  JobQueue jobQueue;
-  std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
-                                       &jobQueue, &idwTable, output_format, &gainLUT,
-                                       display_boost]() -> void {
-    size_t width = yuv420_image_ptr->width;
-    size_t height = yuv420_image_ptr->height;
-
-    size_t rowStart, rowEnd;
-    while (jobQueue.dequeueJob(rowStart, rowEnd)) {
-      for (size_t y = rowStart; y < rowEnd; ++y) {
-        for (size_t x = 0; x < width; ++x) {
-          Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
-          // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
-          Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
-          // We are assuming the SDR base image is always sRGB transfer.
-#if USE_SRGB_INVOETF_LUT
-          Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
-#else
-          Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
-#endif
-          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)) {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
-          } else {
-            gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
-          }
-
-#if USE_APPLY_GAIN_LUT
-          Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
-#else
-          Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
-#endif
-          rgb_hdr = rgb_hdr / display_boost;
-          size_t pixel_idx = x + y * width;
-
-          switch (output_format) {
-            case ULTRAHDR_OUTPUT_HDR_LINEAR: {
-              uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
-              reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_HLG: {
-#if USE_HLG_OETF_LUT
-              ColorTransformFn hdrOetf = hlgOetfLUT;
-#else
-              ColorTransformFn hdrOetf = hlgOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            case ULTRAHDR_OUTPUT_HDR_PQ: {
-#if USE_PQ_OETF_LUT
-              ColorTransformFn hdrOetf = pqOetfLUT;
-#else
-              ColorTransformFn hdrOetf = pqOetf;
-#endif
-              Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
-              uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
-              reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
-              break;
-            }
-            default: {
-            }
-              // Should be impossible to hit after input validation.
-          }
-        }
-      }
-    }
-  };
-
-  const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
-  std::vector<std::thread> workers;
-  for (int th = 0; th < threads - 1; th++) {
-    workers.push_back(std::thread(applyRecMap));
-  }
-  const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
-  for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
-    int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
-    jobQueue.enqueueJob(rowStart, rowEnd);
-    rowStart = rowEnd;
-  }
-  jobQueue.markQueueForEnd();
-  applyRecMap();
-  std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
-  return NO_ERROR;
-}
-
-status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
-                                              jr_compressed_ptr primary_jpg_image_ptr,
-                                              jr_compressed_ptr gainmap_jpg_image_ptr) {
-  if (jpegr_image_ptr == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-
-  MessageHandler msg_handler;
-  std::shared_ptr<DataSegment> seg =
-          DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
-                              static_cast<const uint8_t*>(jpegr_image_ptr->data),
-                              DataSegment::BufferDispositionPolicy::kDontDelete);
-  DataSegmentDataSource data_source(seg);
-  JpegInfoBuilder jpeg_info_builder;
-  jpeg_info_builder.SetImageLimit(2);
-  JpegScanner jpeg_scanner(&msg_handler);
-  jpeg_scanner.Run(&data_source, &jpeg_info_builder);
-  data_source.Reset();
-
-  if (jpeg_scanner.HasError()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const auto& jpeg_info = jpeg_info_builder.GetInfo();
-  const auto& image_ranges = jpeg_info.GetImageRanges();
-
-  if (image_ranges.empty()) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  if (primary_jpg_image_ptr != nullptr) {
-    primary_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
-    primary_jpg_image_ptr->length = image_ranges[0].GetLength();
-  }
-
-  if (image_ranges.size() == 1) {
-    return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
-  }
-
-  if (gainmap_jpg_image_ptr != nullptr) {
-    gainmap_jpg_image_ptr->data =
-            static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
-    gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
-  }
-
-  // TODO: choose primary image and gain map image carefully
-  if (image_ranges.size() > 2) {
-    ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
-          (int)image_ranges.size());
-  }
-
-  return NO_ERROR;
-}
-
-// JPEG/R structure:
-// SOI (ff d8)
-//
-// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
-// in the JPEG input (Encode API-2, API-3, API-4))
-// APP1 (ff e1)
-// 2 bytes of length (2 + length of exif package)
-// EXIF package (this includes the first two bytes representing the package length)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (Required, MPF package) APP2 (ff e2)
-// 2 bytes of length
-// MPF
-//
-// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
-//
-// SOI (ff d8)
-//
-// (Required, XMP package) APP1 (ff e1)
-// 2 bytes of length (2 + 29 + length of xmp package)
-// name space ("http://ns.adobe.com/xap/1.0/\0")
-// XMP
-//
-// (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::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
-                              jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
-                              void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
-                              jr_compressed_ptr dest) {
-  if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
-      dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (metadata->version.compare("1.0")) {
-    ALOGE("received bad value for version: %s", metadata->version.c_str());
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->maxContentBoost < metadata->minContentBoost) {
-    ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
-          metadata->maxContentBoost);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
-    ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
-          metadata->hdrCapacityMax);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
-    ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  if (metadata->gamma <= 0.0f) {
-    ALOGE("received bad value for gamma %f", metadata->gamma);
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-
-  const string nameSpace = "http://ns.adobe.com/xap/1.0/";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  // calculate secondary image length first, because the length will be written into the primary
-  // image xmp
-  const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
-  const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
-          + nameSpaceLength          /* 29 bytes length of name space including \0 */
-          + xmp_secondary.size();    /* length of xmp packet */
-  const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
-          + xmp_secondary_length + gainmap_jpg_image_ptr->length;
-  // primary image
-  const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
-  // same as primary
-  const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
-
-  // Check if EXIF package presents in the JPEG input.
-  // If so, extract and remove the EXIF package.
-  JpegDecoderHelper decoder;
-  if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
-    return ERROR_JPEGR_DECODE_ERROR;
-  }
-  jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
-  jpegr_compressed_struct new_jpg_image = {.data = nullptr,
-                                           .length = 0,
-                                           .maxLength = 0,
-                                           .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  std::unique_ptr<uint8_t[]> dest_data;
-  if (decoder.getEXIFPos() >= 0) {
-    if (pExif != nullptr) {
-      ALOGE("received EXIF from outside while the primary image already contains EXIF");
-      return ERROR_JPEGR_INVALID_INPUT_TYPE;
-    }
-    copyJpegWithoutExif(&new_jpg_image,
-                        primary_jpg_image_ptr,
-                        decoder.getEXIFPos(),
-                        decoder.getEXIFSize());
-    dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
-    exif_from_jpg.data = decoder.getEXIFPtr();
-    exif_from_jpg.length = decoder.getEXIFSize();
-    pExif = &exif_from_jpg;
-  }
-
-  jr_compressed_ptr final_primary_jpg_image_ptr =
-          new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
-
-  int pos = 0;
-  // Begin primary image
-  // 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));
-
-  // Write EXIF
-  if (pExif != nullptr) {
-    const int length = 2 + pExif->length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
-  }
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_primary_length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
-  }
-
-  // Write ICC
-  if (pIcc != nullptr && icc_size > 0) {
-    const int length = icc_size + 2;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
-  }
-
-  // Prepare and write MPF
-  {
-    const int length = 2 + calculateMpfSize();
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
-    // between APP2 + package size + signature
-    // ff e2 00 58 4d 50 46 00
-    // 2 + 2 + 4 = 8 (bytes)
-    // and ff d8 sign of the secondary image
-    int secondary_image_offset = primary_image_size - pos - 8;
-    sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
-                                     secondary_image_size, secondary_image_offset);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
-  }
-
-  // Write primary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
-                    final_primary_jpg_image_ptr->length - 2, pos));
-  // Finish primary image
-
-  // 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));
-
-  // Prepare and write XMP
-  {
-    const int length = xmp_secondary_length;
-    const uint8_t lengthH = ((length >> 8) & 0xff);
-    const uint8_t lengthL = (length & 0xff);
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
-    JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
-    JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
-    JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
-    JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
-  }
-
-  // Write secondary image
-  JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
-                    gainmap_jpg_image_ptr->length - 2, pos));
-
-  // Set back length
-  dest->length = pos;
-
-  // Done!
-  return NO_ERROR;
-}
-
-status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
-  if (src == nullptr || dest == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src->width != dest->width || src->height != dest->height) {
-    return ERROR_JPEGR_INVALID_INPUT_TYPE;
-  }
-  uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
-  uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
-  for (size_t y = 0; y < src->height; ++y) {
-    uint16_t* src_y_row = src_y_data + y * src->luma_stride;
-    uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
-    for (size_t x = 0; x < src->width; ++x) {
-      uint16_t y_uint = src_y_row[x] >> 6;
-      dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
-    }
-    if (dest->width != dest->luma_stride) {
-      memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
-    }
-  }
-  uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
-  uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
-  size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
-  uint8_t* dst_v_data = dst_u_data + dst_v_offset;
-  for (size_t y = 0; y < src->height / 2; ++y) {
-    uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
-    uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
-    uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
-    for (size_t x = 0; x < src->width / 2; ++x) {
-      uint16_t u_uint = src_uv_row[x << 1] >> 6;
-      uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
-      dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
-      dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
-    }
-    if (dest->width / 2 != dest->chroma_stride) {
-      memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-      memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
-    }
-  }
-  dest->colorGamut = src->colorGamut;
-  return NO_ERROR;
-}
-
-status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
-                           ultrahdr_color_gamut dest_encoding) {
-  if (image == nullptr) {
-    return ERROR_JPEGR_INVALID_NULL_PTR;
-  }
-  if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
-      dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  ColorTransformFn conversionFn = nullptr;
-  switch (src_encoding) {
-    case ULTRAHDR_COLORGAMUT_BT709:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv709To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv709To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_P3:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv601To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          return NO_ERROR;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          conversionFn = yuv601To2100;
-          break;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    case ULTRAHDR_COLORGAMUT_BT2100:
-      switch (dest_encoding) {
-        case ULTRAHDR_COLORGAMUT_BT709:
-          conversionFn = yuv2100To709;
-          break;
-        case ULTRAHDR_COLORGAMUT_P3:
-          conversionFn = yuv2100To601;
-          break;
-        case ULTRAHDR_COLORGAMUT_BT2100:
-          return NO_ERROR;
-        default:
-          // Should be impossible to hit after input validation
-          return ERROR_JPEGR_INVALID_COLORGAMUT;
-      }
-      break;
-    default:
-      // Should be impossible to hit after input validation
-      return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  if (conversionFn == nullptr) {
-    // Should be impossible to hit after input validation
-    return ERROR_JPEGR_INVALID_COLORGAMUT;
-  }
-
-  for (size_t y = 0; y < image->height / 2; ++y) {
-    for (size_t x = 0; x < image->width / 2; ++x) {
-      transformYuv420(image, x, y, conversionFn);
-    }
-  }
-
-  return NO_ERROR;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
deleted file mode 100644
index c434eb6..0000000
--- a/libs/ultrahdr/jpegrutils.cpp
+++ /dev/null
@@ -1,600 +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 <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 <utils/Log.h>
-
-using namespace photos_editing_formats::image_io;
-using namespace std;
-
-namespace android::ultrahdr {
-/*
- * Helper function used for generating XMP metadata.
- *
- * @param prefix The prefix part of the name.
- * @param suffix The suffix part of the name.
- * @return A name of the form "prefix:suffix".
- */
-static inline string Name(const string &prefix, const string &suffix) {
-  std::stringstream ss;
-  ss << prefix << ":" << suffix;
-  return ss.str();
-}
-
-DataStruct::DataStruct(int s) {
-    data = malloc(s);
-    length = s;
-    memset(data, 0, s);
-    writePos = 0;
-}
-
-DataStruct::~DataStruct() {
-    if (data != nullptr) {
-        free(data);
-    }
-}
-
-void* DataStruct::getData() {
-    return data;
-}
-
-int DataStruct::getLength() {
-    return length;
-}
-
-int DataStruct::getBytesWritten() {
-    return writePos;
-}
-
-bool DataStruct::write8(uint8_t value) {
-    uint8_t v = value;
-    return write(&v, 1);
-}
-
-bool DataStruct::write16(uint16_t value) {
-    uint16_t v = value;
-    return write(&v, 2);
-}
-bool DataStruct::write32(uint32_t value) {
-    uint32_t v = value;
-    return write(&v, 4);
-}
-
-bool DataStruct::write(const void* src, int size) {
-    if (writePos + size > length) {
-        ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
-                writePos, size, length);
-        return false;
-    }
-    memcpy((uint8_t*) data + writePos, src, size);
-    writePos += size;
-    return true;
-}
-
-/*
- * Helper function used for writing data to destination.
- */
-status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
-  if (position + length > destination->maxLength) {
-    return ERROR_JPEGR_BUFFER_TOO_SMALL;
-  }
-
-  memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
-  position += length;
-  return NO_ERROR;
-}
-
-// Extremely simple XML Handler - just searches for interesting elements
-class XMPXmlHandler : public XmlHandler {
-public:
-
-    XMPXmlHandler() : XmlHandler() {
-        state = NotStrarted;
-        versionFound = false;
-        minContentBoostFound = false;
-        maxContentBoostFound = false;
-        gammaFound = false;
-        offsetSdrFound = false;
-        offsetHdrFound = false;
-        hdrCapacityMinFound = false;
-        hdrCapacityMaxFound = false;
-        baseRenditionIsHdrFound = false;
-    }
-
-    enum ParseState {
-        NotStrarted,
-        Started,
-        Done
-    };
-
-    virtual DataMatchResult StartElement(const XmlTokenContext& context) {
-        string val;
-        if (context.BuildTokenValue(&val)) {
-            if (!val.compare(containerName)) {
-                state = Started;
-            } else {
-                if (state != Done) {
-                    state = NotStrarted;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
-        if (state == Started) {
-            state = Done;
-            lastAttributeName = "";
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val)) {
-                if (!val.compare(versionAttrName)) {
-                    lastAttributeName = versionAttrName;
-                } else if (!val.compare(maxContentBoostAttrName)) {
-                    lastAttributeName = maxContentBoostAttrName;
-                } else if (!val.compare(minContentBoostAttrName)) {
-                    lastAttributeName = minContentBoostAttrName;
-                } else if (!val.compare(gammaAttrName)) {
-                    lastAttributeName = gammaAttrName;
-                } else if (!val.compare(offsetSdrAttrName)) {
-                    lastAttributeName = offsetSdrAttrName;
-                } else if (!val.compare(offsetHdrAttrName)) {
-                    lastAttributeName = offsetHdrAttrName;
-                } else if (!val.compare(hdrCapacityMinAttrName)) {
-                    lastAttributeName = hdrCapacityMinAttrName;
-                } else if (!val.compare(hdrCapacityMaxAttrName)) {
-                    lastAttributeName = hdrCapacityMaxAttrName;
-                } else if (!val.compare(baseRenditionIsHdrAttrName)) {
-                    lastAttributeName = baseRenditionIsHdrAttrName;
-                } else {
-                    lastAttributeName = "";
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
-        string val;
-        if (state == Started) {
-            if (context.BuildTokenValue(&val, true)) {
-                if (!lastAttributeName.compare(versionAttrName)) {
-                    versionStr = val;
-                    versionFound = true;
-                } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
-                    maxContentBoostStr = val;
-                    maxContentBoostFound = true;
-                } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
-                    minContentBoostStr = val;
-                    minContentBoostFound = true;
-                } else if (!lastAttributeName.compare(gammaAttrName)) {
-                    gammaStr = val;
-                    gammaFound = true;
-                } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
-                    offsetSdrStr = val;
-                    offsetSdrFound = true;
-                } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
-                    offsetHdrStr = val;
-                    offsetHdrFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
-                    hdrCapacityMinStr = val;
-                    hdrCapacityMinFound = true;
-                } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
-                    hdrCapacityMaxStr = val;
-                    hdrCapacityMaxFound = true;
-                } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
-                    baseRenditionIsHdrStr = val;
-                    baseRenditionIsHdrFound = true;
-                }
-            }
-        }
-        return context.GetResult();
-    }
-
-    bool getVersion(string* version, bool* present) {
-        if (state == Done) {
-            *version = versionStr;
-            *present = versionFound;
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    bool getMaxContentBoost(float* max_content_boost, bool* present) {
-        if (state == Done) {
-            *present = maxContentBoostFound;
-            stringstream ss(maxContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *max_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getMinContentBoost(float* min_content_boost, bool* present) {
-        if (state == Done) {
-            *present = minContentBoostFound;
-            stringstream ss(minContentBoostStr);
-            float val;
-            if (ss >> val) {
-                *min_content_boost = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-    bool getGamma(float* gamma, bool* present) {
-        if (state == Done) {
-            *present = gammaFound;
-            stringstream ss(gammaStr);
-            float val;
-            if (ss >> val) {
-                *gamma = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetSdr(float* offset_sdr, bool* present) {
-        if (state == Done) {
-            *present = offsetSdrFound;
-            stringstream ss(offsetSdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_sdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getOffsetHdr(float* offset_hdr, bool* present) {
-        if (state == Done) {
-            *present = offsetHdrFound;
-            stringstream ss(offsetHdrStr);
-            float val;
-            if (ss >> val) {
-                *offset_hdr = val;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMinFound;
-            stringstream ss(hdrCapacityMinStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_min = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
-        if (state == Done) {
-            *present = hdrCapacityMaxFound;
-            stringstream ss(hdrCapacityMaxStr);
-            float val;
-            if (ss >> val) {
-                *hdr_capacity_max = exp2(val);
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-    bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
-        if (state == Done) {
-            *present = baseRenditionIsHdrFound;
-            if (!baseRenditionIsHdrStr.compare("False")) {
-                *base_rendition_is_hdr = false;
-                return true;
-            } else if (!baseRenditionIsHdrStr.compare("True")) {
-                *base_rendition_is_hdr = true;
-                return true;
-            } else {
-                return false;
-            }
-        } else {
-            return false;
-        }
-    }
-
-
-
-private:
-    static const string containerName;
-
-    static const string versionAttrName;
-    string              versionStr;
-    bool                versionFound;
-    static const string maxContentBoostAttrName;
-    string              maxContentBoostStr;
-    bool                maxContentBoostFound;
-    static const string minContentBoostAttrName;
-    string              minContentBoostStr;
-    bool                minContentBoostFound;
-    static const string gammaAttrName;
-    string              gammaStr;
-    bool                gammaFound;
-    static const string offsetSdrAttrName;
-    string              offsetSdrStr;
-    bool                offsetSdrFound;
-    static const string offsetHdrAttrName;
-    string              offsetHdrStr;
-    bool                offsetHdrFound;
-    static const string hdrCapacityMinAttrName;
-    string              hdrCapacityMinStr;
-    bool                hdrCapacityMinFound;
-    static const string hdrCapacityMaxAttrName;
-    string              hdrCapacityMaxStr;
-    bool                hdrCapacityMaxFound;
-    static const string baseRenditionIsHdrAttrName;
-    string              baseRenditionIsHdrStr;
-    bool                baseRenditionIsHdrFound;
-
-    string              lastAttributeName;
-    ParseState          state;
-};
-
-// GContainer XMP constants - URI and namespace prefix
-const string kContainerUri        = "http://ns.google.com/photos/1.0/container/";
-const string kContainerPrefix     = "Container";
-
-// GContainer XMP constants - element and attribute names
-const string kConDirectory            = Name(kContainerPrefix, "Directory");
-const string kConItem                 = Name(kContainerPrefix, "Item");
-
-// GContainer XMP constants - names for XMP handlers
-const string XMPXmlHandler::containerName = "rdf:Description";
-// Item XMP constants - URI and namespace prefix
-const string kItemUri        = "http://ns.google.com/photos/1.0/container/item/";
-const string kItemPrefix     = "Item";
-
-// Item XMP constants - element and attribute names
-const string kItemLength           = Name(kItemPrefix, "Length");
-const string kItemMime             = Name(kItemPrefix, "Mime");
-const string kItemSemantic         = Name(kItemPrefix, "Semantic");
-
-// Item XMP constants - element and attribute values
-const string kSemanticPrimary = "Primary";
-const string kSemanticGainMap = "GainMap";
-const string kMimeImageJpeg   = "image/jpeg";
-
-// GainMap XMP constants - URI and namespace prefix
-const string kGainMapUri      = "http://ns.adobe.com/hdr-gain-map/1.0/";
-const string kGainMapPrefix   = "hdrgm";
-
-// 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");
-
-// GainMap XMP constants - names for XMP handlers
-const string XMPXmlHandler::versionAttrName = kMapVersion;
-const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
-const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
-const string XMPXmlHandler::gammaAttrName = kMapGamma;
-const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
-const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
-const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
-const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
-const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
-
-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) {
-        // Data too short
-        return false;
-    }
-
-    if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
-        // Not correct namespace
-        return false;
-    }
-
-    // Position the pointers to the start of XMP XML portion
-    xmp_data += nameSpace.size()+1;
-    xmp_size -= nameSpace.size()+1;
-    XMPXmlHandler handler;
-
-    // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
-    while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
-        xmp_size--;
-    }
-
-    string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
-    MessageHandler msg_handler;
-    unique_ptr<XmlRule> rule(new XmlElementRule);
-    XmlReader reader(&handler, &msg_handler);
-    reader.StartParse(std::move(rule));
-    reader.Parse(str);
-    reader.FinishParse();
-    if (reader.HasErrors()) {
-        // Parse error
-        return false;
-    }
-
-    // Apply default values to any not-present fields, except for Version,
-    // maxContentBoost, and hdrCapacityMax, which are required. Return false if
-    // we encounter a present field that couldn't be parsed, since this
-    // indicates it is invalid (eg. string where there should be a float).
-    bool present = false;
-    if (!handler.getVersion(&metadata->version, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) {
-        return false;
-    }
-    if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) {
-        return false;
-    }
-    if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) {
-        if (present) return false;
-        metadata->minContentBoost = 1.0f;
-    }
-    if (!handler.getGamma(&metadata->gamma, &present)) {
-        if (present) return false;
-        metadata->gamma = 1.0f;
-    }
-    if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) {
-        if (present) return false;
-        metadata->offsetSdr = 1.0f / 64.0f;
-    }
-    if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) {
-        if (present) return false;
-        metadata->offsetHdr = 1.0f / 64.0f;
-    }
-    if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) {
-        if (present) return false;
-        metadata->hdrCapacityMin = 1.0f;
-    }
-
-    bool base_rendition_is_hdr;
-    if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
-        if (present) return false;
-        base_rendition_is_hdr = false;
-    }
-    if (base_rendition_is_hdr) {
-        ALOGE("Base rendition of HDR is not supported!");
-        return false;
-    }
-
-    return true;
-}
-
-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});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  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.StartWritingElement("rdf:li");
-  writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
-  writer.StartWritingElement(kConItem);
-  writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
-  writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
-  writer.FinishWritingElementsToDepth(item_depth);
-
-  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(ultrahdr_metadata_struct& metadata) {
-  const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
-
-  std::stringstream ss;
-  photos_editing_formats::image_io::XmlWriter writer(ss);
-  writer.StartWritingElement("x:xmpmeta");
-  writer.WriteXmlns("x", "adobe:ns:meta/");
-  writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
-  writer.StartWritingElement("rdf:RDF");
-  writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
-  writer.StartWritingElement("rdf:Description");
-  writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
-  writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
-  writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
-  writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
-  writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr);
-  writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr);
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin));
-  writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax));
-  writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
-  writer.FinishWriting();
-
-  return ss.str();
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/multipictureformat.cpp b/libs/ultrahdr/multipictureformat.cpp
deleted file mode 100644
index f1679ef..0000000
--- a/libs/ultrahdr/multipictureformat.cpp
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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 <ultrahdr/multipictureformat.h>
-#include <ultrahdr/jpegrutils.h>
-
-namespace android::ultrahdr {
-size_t calculateMpfSize() {
-    return sizeof(kMpfSig) +                 // Signature
-            kMpEndianSize +                   // Endianness
-            sizeof(uint32_t) +                // Index IFD Offset
-            sizeof(uint16_t) +                // Tag count
-            kTagSerializedCount * kTagSize +  // 3 tags at 12 bytes each
-            sizeof(uint32_t) +                // Attribute IFD offset
-            kNumPictures * kMPEntrySize;      // MP Entries for each image
-}
-
-sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
-        int secondary_image_size, int secondary_image_offset) {
-    size_t mpf_size = calculateMpfSize();
-    sp<DataStruct> dataStruct = sp<DataStruct>::make(mpf_size);
-
-    dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
-#if USE_BIG_ENDIAN
-    dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize);
-#else
-    dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize);
-#endif
-
-    // Set the Index IFD offset be the position after the endianness value and this offset.
-    constexpr uint32_t indexIfdOffset =
-            static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig));
-    dataStruct->write32(Endian_SwapBE32(indexIfdOffset));
-
-    // We will write 3 tags (version, number of images, MP entries).
-    dataStruct->write16(Endian_SwapBE16(kTagSerializedCount));
-
-    // Write the version tag.
-    dataStruct->write16(Endian_SwapBE16(kVersionTag));
-    dataStruct->write16(Endian_SwapBE16(kVersionType));
-    dataStruct->write32(Endian_SwapBE32(kVersionCount));
-    dataStruct->write(kVersionExpected, kVersionSize);
-
-    // Write the number of images.
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag));
-    dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType));
-    dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount));
-    dataStruct->write32(Endian_SwapBE32(kNumPictures));
-
-    // Write the MP entries.
-    dataStruct->write16(Endian_SwapBE16(kMPEntryTag));
-    dataStruct->write16(Endian_SwapBE16(kMPEntryType));
-    dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures));
-    const uint32_t mpEntryOffset =
-            static_cast<uint32_t>(dataStruct->getBytesWritten() -  // The bytes written so far
-                                  sizeof(kMpfSig) +   // Excluding the MPF signature
-                                  sizeof(uint32_t) +  // The 4 bytes for this offset
-                                  sizeof(uint32_t));  // The 4 bytes for the attribute IFD offset.
-    dataStruct->write32(Endian_SwapBE32(mpEntryOffset));
-
-    // Write the attribute IFD offset (zero because we don't write it).
-    dataStruct->write32(0);
-
-    // Write the MP entries for primary image
-    dataStruct->write32(
-            Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary));
-    dataStruct->write32(Endian_SwapBE32(primary_image_size));
-    dataStruct->write32(Endian_SwapBE32(primary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    // Write the MP entries for secondary image
-    dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_size));
-    dataStruct->write32(Endian_SwapBE32(secondary_image_offset));
-    dataStruct->write16(0);
-    dataStruct->write16(0);
-
-    return dataStruct;
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/Android.bp b/libs/ultrahdr/tests/Android.bp
deleted file mode 100644
index 00cc797..0000000
--- a/libs/ultrahdr/tests/Android.bp
+++ /dev/null
@@ -1,51 +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.
-
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_native_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_test {
-    name: "ultrahdr_unit_test-deprecated",
-    enabled: false,
-    test_suites: ["device-tests"],
-    srcs: [
-        "gainmapmath_test.cpp",
-        "icchelper_test.cpp",
-        "jpegr_test.cpp",
-        "jpegencoderhelper_test.cpp",
-        "jpegdecoderhelper_test.cpp",
-    ],
-    shared_libs: [
-        "libimage_io",
-        "libjpeg",
-        "liblog",
-    ],
-    static_libs: [
-        "libgmock",
-        "libgtest",
-        "libjpegdecoder",
-        "libjpegencoder",
-        "libultrahdr",
-        "libutils",
-    ],
-    data: [
-        "./data/*.*",
-    ],
-}
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
deleted file mode 100644
index 1754a5c..0000000
--- a/libs/ultrahdr/tests/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<configuration description="Unit test configuration for ultrahdr_unit_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
-        <option name="push" value="data/*->/data/local/tmp/" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/local/tmp" />
-        <option name="module-name" value="ultrahdr_unit_test" />
-    </test>
-</configuration>
diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg
deleted file mode 100644
index e285742..0000000
--- a/libs/ultrahdr/tests/data/jpeg_image.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12
deleted file mode 100644
index 7b2fc71..0000000
--- a/libs/ultrahdr/tests/data/minnie-318x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
deleted file mode 100644
index 20b5a2c..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
deleted file mode 100644
index c7f4538..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
deleted file mode 100644
index 41300f4..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y
deleted file mode 100644
index f9d8371..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.y
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.yu12 b/libs/ultrahdr/tests/data/minnie-320x240.yu12
deleted file mode 100644
index 0d66f53..0000000
--- a/libs/ultrahdr/tests/data/minnie-320x240.yu12
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_p010_image.p010 b/libs/ultrahdr/tests/data/raw_p010_image.p010
deleted file mode 100644
index 01673bf..0000000
--- a/libs/ultrahdr/tests/data/raw_p010_image.p010
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420 b/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
deleted file mode 100644
index c043da6..0000000
--- a/libs/ultrahdr/tests/data/raw_yuv420_image.yuv420
+++ /dev/null
Binary files differ
diff --git a/libs/ultrahdr/tests/gainmapmath_test.cpp b/libs/ultrahdr/tests/gainmapmath_test.cpp
deleted file mode 100644
index 7c2d076..0000000
--- a/libs/ultrahdr/tests/gainmapmath_test.cpp
+++ /dev/null
@@ -1,1359 +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 <cmath>
-#include <gtest/gtest.h>
-#include <gmock/gmock.h>
-#include <ultrahdr/gainmapmath.h>
-
-namespace android::ultrahdr {
-
-class GainMapMathTest : public testing::Test {
-public:
-  GainMapMathTest();
-  ~GainMapMathTest();
-
-  float ComparisonEpsilon() { return 1e-4f; }
-  float LuminanceEpsilon() { return 1e-2f; }
-  float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
-
-  Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
-      return {{{ static_cast<float>(y) / 255.0f,
-                 (static_cast<float>(u) - 128.0f) / 255.0f,
-                 (static_cast<float>(v) - 128.0f) / 255.0f }}};
-  }
-
-  Color P010(uint16_t y, uint16_t u, uint16_t v) {
-      return {{{ (static_cast<float>(y) - 64.0f) / 876.0f,
-                 (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
-                 (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f }}};
-  }
-
-  float Map(uint8_t e) {
-    return static_cast<float>(e) / 255.0f;
-  }
-
-  Color ColorMin(Color e1, Color e2) {
-    return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
-  }
-
-  Color ColorMax(Color e1, Color e2) {
-    return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
-  }
-
-  Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
-
-  Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-  Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
-  Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
-
-  Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
-  Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
-
-  Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; }
-  Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; }
-  Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; }
-
-  Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; }
-  Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; }
-  Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; }
-
-  Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
-  Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
-  Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
-
-  float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
-    Color rgb_gamma = p3YuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * kSdrWhiteNits;
-  }
-
-  float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
-                             ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
-                             float scale_factor) {
-    Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
-    Color rgb = hdrInvOetf(rgb_gamma);
-    rgb = gamutConversionFn(rgb);
-    float luminance_scaled = luminanceFn(rgb);
-    return luminance_scaled * scale_factor;
-  }
-
-  Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) {
-    Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
-    Color rgb = srgbInvOetf(rgb_gamma);
-    return applyGain(rgb, gain, metadata);
-  }
-
-  jpegr_uncompressed_struct Yuv420Image() {
-    static uint8_t pixels[] = {
-      // Y
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-      // U
-      0xA0, 0xA1,
-      0xA2, 0xA3,
-      // V
-      0xB0, 0xB1,
-      0xB2, 0xB3,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
-  }
-
-  Color (*Yuv420Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
-        Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
-        Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
-      }, {
-        Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
-        Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
-      }, {
-        Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
-        Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct P010Image() {
-    static uint16_t pixels[] = {
-      // Y
-      0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
-      0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
-      0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
-      0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
-      // UV
-      0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
-      0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
-  }
-
-  Color (*P010Colors())[4] {
-    static Color colors[4][4] = {
-      {
-        P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
-        P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
-      }, {
-        P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
-        P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
-      }, {
-        P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
-        P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
-      }, {
-        P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
-        P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
-      },
-    };
-    return colors;
-  }
-
-  jpegr_uncompressed_struct MapImage() {
-    static uint8_t pixels[] = {
-      0x00, 0x10, 0x20, 0x30,
-      0x01, 0x11, 0x21, 0x31,
-      0x02, 0x12, 0x22, 0x32,
-      0x03, 0x13, 0x23, 0x33,
-    };
-    return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED };
-  }
-
-  float (*MapValues())[4] {
-    static float values[4][4] = {
-      {
-        Map(0x00), Map(0x10), Map(0x20), Map(0x30),
-      }, {
-        Map(0x01), Map(0x11), Map(0x21), Map(0x31),
-      }, {
-        Map(0x02), Map(0x12), Map(0x22), Map(0x32),
-      }, {
-        Map(0x03), Map(0x13), Map(0x23), Map(0x33),
-      },
-    };
-    return values;
-  }
-
-protected:
-  virtual void SetUp();
-  virtual void TearDown();
-};
-
-GainMapMathTest::GainMapMathTest() {}
-GainMapMathTest::~GainMapMathTest() {}
-
-void GainMapMathTest::SetUp() {}
-void GainMapMathTest::TearDown() {}
-
-#define EXPECT_RGB_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).r, (e2).r);  \
-  EXPECT_FLOAT_EQ((e1).g, (e2).g);  \
-  EXPECT_FLOAT_EQ((e1).b, (e2).b)
-
-#define EXPECT_RGB_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
-
-#define EXPECT_RGB_CLOSE(e1, e2)                            \
-  EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
-  EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
-
-#define EXPECT_YUV_EQ(e1, e2)       \
-  EXPECT_FLOAT_EQ((e1).y, (e2).y);  \
-  EXPECT_FLOAT_EQ((e1).u, (e2).u);  \
-  EXPECT_FLOAT_EQ((e1).v, (e2).v)
-
-#define EXPECT_YUV_NEAR(e1, e2)                     \
-  EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
-  EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
-
-#define EXPECT_YUV_BETWEEN(e, min, max)                                           \
-  EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
-  EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
-  EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
-
-// TODO: a bunch of these tests can be parameterized.
-
-TEST_F(GainMapMathTest, ColorConstruct) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  EXPECT_FLOAT_EQ(e1.r, 0.1f);
-  EXPECT_FLOAT_EQ(e1.g, 0.2f);
-  EXPECT_FLOAT_EQ(e1.b, 0.3f);
-
-  EXPECT_FLOAT_EQ(e1.y, 0.1f);
-  EXPECT_FLOAT_EQ(e1.u, 0.2f);
-  EXPECT_FLOAT_EQ(e1.v, 0.3f);
-}
-
-TEST_F(GainMapMathTest, ColorAddColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 += e1;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
-}
-
-TEST_F(GainMapMathTest, ColorAddFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 + 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
-
-  e2 += 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractColor) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - e1;
-  EXPECT_FLOAT_EQ(e2.r, 0.0f);
-  EXPECT_FLOAT_EQ(e2.g, 0.0f);
-  EXPECT_FLOAT_EQ(e2.b, 0.0f);
-
-  e2 -= e1;
-  EXPECT_FLOAT_EQ(e2.r, -e1.r);
-  EXPECT_FLOAT_EQ(e2.g, -e1.g);
-  EXPECT_FLOAT_EQ(e2.b, -e1.b);
-}
-
-TEST_F(GainMapMathTest, ColorSubtractFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 - 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
-
-  e2 -= 0.1f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
-}
-
-TEST_F(GainMapMathTest, ColorMultiplyFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 * 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
-
-  e2 *= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
-}
-
-TEST_F(GainMapMathTest, ColorDivideFloat) {
-  Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
-
-  Color e2 = e1 / 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
-
-  e2 /= 2.0f;
-  EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
-  EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
-  EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
-}
-
-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);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
-  EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
-}
-
-TEST_F(GainMapMathTest, SrgbYuvToRgb) {
-  Color rgb_black = srgbYuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbToYuv) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
-  Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-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());
-  EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
-}
-
-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);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
-  EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
-}
-
-TEST_F(GainMapMathTest, P3YuvToRgb) {
-  Color rgb_black = p3YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(P3YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(P3YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(P3YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbToYuv) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
-  Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-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);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
-  EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
-}
-
-TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
-  Color rgb_black = bt2100YuvToRgb(YuvBlack());
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(YuvWhite());
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv_black, YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv_white, YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
-  Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
-  EXPECT_RGB_NEAR(rgb_black, RgbBlack());
-
-  Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
-  EXPECT_RGB_NEAR(rgb_white, RgbWhite());
-
-  Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
-  EXPECT_RGB_NEAR(rgb_r, RgbRed());
-
-  Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
-  EXPECT_RGB_NEAR(rgb_g, RgbGreen());
-
-  Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
-  EXPECT_RGB_NEAR(rgb_b, RgbBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) {
-  Color yuv_black = srgbRgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = srgbRgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = srgbRgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = srgbRgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = srgbRgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) {
-  Color yuv_black = p3RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack());
-
-  Color yuv_white = p3RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite());
-
-  Color yuv_r = p3RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed());
-
-  Color yuv_g = p3RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen());
-
-  Color yuv_b = p3RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue());
-}
-
-TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) {
-  Color yuv_black = bt2100RgbToYuv(RgbBlack());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack());
-
-  Color yuv_white = bt2100RgbToYuv(RgbWhite());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite());
-
-  Color yuv_r = bt2100RgbToYuv(RgbRed());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed());
-
-  Color yuv_g = bt2100RgbToYuv(RgbGreen());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen());
-
-  Color yuv_b = bt2100RgbToYuv(RgbBlue());
-  EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue());
-}
-
-TEST_F(GainMapMathTest, TransformYuv420) {
-  ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100,
-                                    yuv2100To709, yuv2100To601 };
-  for (const ColorTransformFn& transform : transforms) {
-    jpegr_uncompressed_struct input = Yuv420Image();
-
-    size_t out_buf_size = input.width * input.height * 3 / 2;
-    std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
-    memcpy(out_buf.get(), input.data, out_buf_size);
-    jpegr_uncompressed_struct output = Yuv420Image();
-    output.data = out_buf.get();
-    output.chroma_data = out_buf.get() + input.width * input.height;
-    output.luma_stride = input.width;
-    output.chroma_stride = input.width / 2;
-
-    transformYuv420(&output, 1, 1, transform);
-
-    for (size_t y = 0; y < 4; ++y) {
-      for (size_t x = 0; x < 4; ++x) {
-        // Skip the last chroma sample, which we modified above
-        if (x >= 2 && y >= 2) {
-          continue;
-        }
-
-        // All other pixels should remain unchanged
-        EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y));
-      }
-    }
-
-    // modified pixels should be updated as intended by the transformYuv420 algorithm
-    Color in1 = getYuv420Pixel(&input,   2, 2);
-    Color in2 = getYuv420Pixel(&input,   3, 2);
-    Color in3 = getYuv420Pixel(&input,   2, 3);
-    Color in4 = getYuv420Pixel(&input,   3, 3);
-    Color out1 = getYuv420Pixel(&output, 2, 2);
-    Color out2 = getYuv420Pixel(&output, 3, 2);
-    Color out3 = getYuv420Pixel(&output, 2, 3);
-    Color out4 = getYuv420Pixel(&output, 3, 3);
-
-    EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon());
-    EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon());
-
-    Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f;
-
-    EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon());
-
-    EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon());
-    EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon());
-  }
-}
-
-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());
-  EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
-  Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
-  EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
-}
-
-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());
-  EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
-  Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
-  EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
-}
-
-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());
-  EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
-}
-
-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());
-  EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
-
-  Color e = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
-  EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
-}
-
-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());
-  EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
-
-  Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
-  Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
-  EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
-}
-
-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(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(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(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(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(GainMapMathTest, applyGainLUT) {
-  for (int boost = 1; boost <= 10; boost++) {
-    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++) {
-    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++) {
-    ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost),
-                                       .minContentBoost = 1.0f / pow(static_cast<float>(boost),
-                                                              1.0f / 3.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));
-    }
-  }
-}
-
-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());
-  EXPECT_NEAR(pqInvOetf(pqOetf(0.99f)), 0.99f, ComparisonEpsilon());
-  EXPECT_FLOAT_EQ(pqInvOetf(pqOetf(1.0f)), 1.0f);
-}
-
-TEST_F(GainMapMathTest, ColorConversionLookup) {
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt709);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToBt709);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToP3);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3),
-            identityConversion);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100),
-            bt2100ToP3);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709),
-            bt709ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3),
-            p3ToBt2100);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100),
-            identityConversion);
-
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3),
-            nullptr);
-  EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100),
-            nullptr);
-}
-
-TEST_F(GainMapMathTest, EncodeGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-
-  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(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(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(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(encodeGain(0.0f, 0.0f, &metadata), 0);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0);
-
-  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(encodeGain(0.0f, 0.0f, &metadata), 63);
-  EXPECT_EQ(encodeGain(1.0f, 0.0f, &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(GainMapMathTest, ApplyGain) {
-  ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f,
-                                        .minContentBoost = 1.0f / 4.0f };
-  float displayBoost = metadata.maxContentBoost;
-
-  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(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(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(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(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(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(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(GainMapMathTest, GetYuv420Pixel) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getYuv420Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, GetP010Pixel) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  for (size_t y = 0; y < 4; ++y) {
-    for (size_t x = 0; x < 4; ++x) {
-      EXPECT_YUV_NEAR(getP010Pixel(&image, x, y), colors[y][x]);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleYuv420) {
-  jpegr_uncompressed_struct image = Yuv420Image();
-  Color (*colors)[4] = Yuv420Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleYuv420(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleP010) {
-  jpegr_uncompressed_struct image = P010Image();
-  Color (*colors)[4] = P010Colors();
-
-  static const size_t kMapScaleFactor = 2;
-  for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) {
-      Color min = {{{ 1.0f, 1.0f, 1.0f }}};
-      Color max = {{{ -1.0f, -1.0f, -1.0f }}};
-
-      for (size_t dy = 0; dy < kMapScaleFactor; ++dy) {
-        for (size_t dx = 0; dx < kMapScaleFactor; ++dx) {
-          Color e = colors[y * kMapScaleFactor + dy][x * kMapScaleFactor + dx];
-          min = ColorMin(min, e);
-          max = ColorMax(max, e);
-        }
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_YUV_BETWEEN(sampleP010(&image, kMapScaleFactor, x, y), min, max);
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, SampleMap) {
-  jpegr_uncompressed_struct image = MapImage();
-  float (*values)[4] = MapValues();
-
-  static const size_t kMapScaleFactor = 2;
-  ShepardsIDW idwTable(kMapScaleFactor);
-  for (size_t y = 0; y < 4 * kMapScaleFactor; ++y) {
-    for (size_t x = 0; x < 4 * kMapScaleFactor; ++x) {
-      size_t x_base = x / kMapScaleFactor;
-      size_t y_base = y / kMapScaleFactor;
-
-      float min = 1.0f;
-      float max = -1.0f;
-
-      min = fmin(min, values[y_base][x_base]);
-      max = fmax(max, values[y_base][x_base]);
-      if (y_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base]);
-        max = fmax(max, values[y_base + 1][x_base]);
-      }
-      if (x_base + 1 < 4) {
-        min = fmin(min, values[y_base][x_base + 1]);
-        max = fmax(max, values[y_base][x_base + 1]);
-      }
-      if (y_base + 1 < 4 && x_base + 1 < 4) {
-        min = fmin(min, values[y_base + 1][x_base + 1]);
-        max = fmax(max, values[y_base + 1][x_base + 1]);
-      }
-
-      // Instead of reimplementing the sampling algorithm, confirm that the
-      // sample output is within the range of the min and max of the nearest
-      // points.
-      EXPECT_THAT(sampleMap(&image, kMapScaleFactor, x, y),
-                  testing::AllOf(testing::Ge(min), testing::Le(max)));
-      EXPECT_EQ(sampleMap(&image, kMapScaleFactor, x, y, idwTable),
-                sampleMap(&image, kMapScaleFactor, x, y));
-    }
-  }
-}
-
-TEST_F(GainMapMathTest, ColorToRgba1010102) {
-  EXPECT_EQ(colorToRgba1010102(RgbBlack()), 0x3 << 30);
-  EXPECT_EQ(colorToRgba1010102(RgbWhite()), 0xFFFFFFFF);
-  EXPECT_EQ(colorToRgba1010102(RgbRed()), 0x3 << 30 | 0x3ff);
-  EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10);
-  EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20);
-
-  Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}};
-  EXPECT_EQ(colorToRgba1010102(e_gamma),
-            0x3 << 30
-          | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff))
-          | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
-          | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20);
-}
-
-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),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance),
-              srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance),
-              srgbLuminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), srgbLuminance),
-              srgbLuminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance),
-              p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance),
-              p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance),
-              p3Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) {
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance),
-                  0.0f);
-  EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance),
-                  kSdrWhiteNits);
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance),
-              bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance),
-              bt2100Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon());
-  EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), bt2100Luminance),
-              bt2100Luminance(RgbBlue()) * kSdrWhiteNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion,
-                                       bt2100Luminance, kHlgMaxNits),
-                  kHlgMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion,
-                                   bt2100Luminance, kHlgMaxNits),
-              bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon());
-}
-
-TEST_F(GainMapMathTest, GenerateMapLuminancePq) {
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  0.0f);
-  EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-                  kPqMaxNits);
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon());
-  EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion,
-                                       bt2100Luminance, kPqMaxNits),
-              bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon());
-}
-
-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);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata),
-                  RgbRed() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata),
-                  RgbGreen() * 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata),
-                  RgbBlue() * 8.0f);
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata),
-                RgbWhite() * sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata),
-                  RgbRed() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata),
-                  RgbGreen() * sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata),
-                  RgbBlue() * sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata),
-                  RgbRed());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata),
-                  RgbGreen());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata),
-                  RgbBlue());
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite() / sqrt(8.0f));
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata),
-                  RgbRed() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata),
-                  RgbGreen() / sqrt(8.0f));
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata),
-                  RgbBlue() / sqrt(8.0f));
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata),
-                RgbBlack());
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata),
-                  RgbRed() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata),
-                  RgbGreen() / 8.0f);
-  EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata),
-                  RgbBlue() / 8.0f);
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 1.0f;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite());
-
-  metadata.maxContentBoost = 8.0f;
-  metadata.minContentBoost = 0.5f;;
-
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata),
-                RgbWhite() * 8.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata),
-                RgbWhite() * 4.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata),
-                RgbWhite() * 2.0f);
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata),
-                RgbWhite());
-  EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata),
-                RgbWhite() / 2.0f);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/icchelper_test.cpp b/libs/ultrahdr/tests/icchelper_test.cpp
deleted file mode 100644
index ff61c08..0000000
--- a/libs/ultrahdr/tests/icchelper_test.cpp
+++ /dev/null
@@ -1,77 +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 <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/ultrahdr.h>
-#include <utils/Log.h>
-
-namespace android::ultrahdr {
-
-class IccHelperTest : public testing::Test {
-public:
-    IccHelperTest();
-    ~IccHelperTest();
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-};
-
-IccHelperTest::IccHelperTest() {}
-
-IccHelperTest::~IccHelperTest() {}
-
-void IccHelperTest::SetUp() {}
-
-void IccHelperTest::TearDown() {}
-
-TEST_F(IccHelperTest, iccWriteThenRead) {
-    sp<DataStruct> iccBt709 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                         ULTRAHDR_COLORGAMUT_BT709);
-    ASSERT_NE(iccBt709->getLength(), 0);
-    ASSERT_NE(iccBt709->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()),
-              ULTRAHDR_COLORGAMUT_BT709);
-
-    sp<DataStruct> iccP3 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3);
-    ASSERT_NE(iccP3->getLength(), 0);
-    ASSERT_NE(iccP3->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()),
-              ULTRAHDR_COLORGAMUT_P3);
-
-    sp<DataStruct> iccBt2100 = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
-                                                          ULTRAHDR_COLORGAMUT_BT2100);
-    ASSERT_NE(iccBt2100->getLength(), 0);
-    ASSERT_NE(iccBt2100->getData(), nullptr);
-    EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()),
-              ULTRAHDR_COLORGAMUT_BT2100);
-}
-
-TEST_F(IccHelperTest, iccEndianness) {
-    sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709);
-    size_t profile_size = icc->getLength() - kICCIdentifierSize;
-
-    uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize;
-    uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 |
-                            static_cast<uint32_t>(icc_bytes[1]) << 16 |
-                            static_cast<uint32_t>(icc_bytes[2]) << 8 |
-                            static_cast<uint32_t>(icc_bytes[3]);
-
-    EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size);
-}
-
-}  // namespace android::ultrahdr
-
diff --git a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp b/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
deleted file mode 100644
index af0d59e..0000000
--- a/libs/ultrahdr/tests/jpegdecoderhelper_test.cpp
+++ /dev/null
@@ -1,156 +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 <gtest/gtest.h>
-#include <ultrahdr/icc.h>
-#include <ultrahdr/jpegdecoderhelper.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-// No ICC or EXIF
-#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
-#define YUV_IMAGE_SIZE 20193
-// Has ICC and EXIF
-#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
-#define YUV_ICC_IMAGE_SIZE 34266
-// No ICC or EXIF
-#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
-#define GREY_IMAGE_SIZE 20193
-
-#define IMAGE_WIDTH 320
-#define IMAGE_HEIGHT 240
-
-class JpegDecoderHelperTest : public testing::Test {
-public:
-    struct Image {
-        std::unique_ptr<uint8_t[]> buffer;
-        size_t size;
-    };
-    JpegDecoderHelperTest();
-    ~JpegDecoderHelperTest();
-
-protected:
-    virtual void SetUp();
-    virtual void TearDown();
-
-    Image mYuvImage, mYuvIccImage, mGreyImage;
-};
-
-JpegDecoderHelperTest::JpegDecoderHelperTest() {}
-
-JpegDecoderHelperTest::~JpegDecoderHelperTest() {}
-
-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[], JpegDecoderHelperTest::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 JpegDecoderHelperTest::SetUp() {
-    if (!loadFile(YUV_IMAGE, &mYuvImage)) {
-        FAIL() << "Load file " << YUV_IMAGE << " failed";
-    }
-    mYuvImage.size = YUV_IMAGE_SIZE;
-    if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) {
-        FAIL() << "Load file " << YUV_ICC_IMAGE << " failed";
-    }
-    mYuvIccImage.size = YUV_ICC_IMAGE_SIZE;
-    if (!loadFile(GREY_IMAGE, &mGreyImage)) {
-        FAIL() << "Load file " << GREY_IMAGE << " failed";
-    }
-    mGreyImage.size = GREY_IMAGE_SIZE;
-}
-
-void JpegDecoderHelperTest::TearDown() {}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_UNSPECIFIED);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-    EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()),
-              ULTRAHDR_COLORGAMUT_BT709);
-}
-
-TEST_F(JpegDecoderHelperTest, decodeGreyImage) {
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size));
-    ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width,
-                                                     &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_EQ(icc.size(), 0);
-    EXPECT_EQ(exif.size(), 0);
-}
-
-TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) {
-    size_t width = 0, height = 0;
-    std::vector<uint8_t> icc, exif;
-
-    JpegDecoderHelper decoder;
-    EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size,
-                                                     &width, &height, &icc, &exif));
-
-    EXPECT_EQ(width, IMAGE_WIDTH);
-    EXPECT_EQ(height, IMAGE_HEIGHT);
-    EXPECT_GT(icc.size(), 0);
-    EXPECT_GT(exif.size(), 0);
-
-    EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709);
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
deleted file mode 100644
index af54eb2..0000000
--- a/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
+++ /dev/null
@@ -1,135 +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 <ultrahdr/jpegencoderhelper.h>
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-#include <fcntl.h>
-
-namespace android::ultrahdr {
-
-#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
-#define ALIGNED_IMAGE_WIDTH 320
-#define ALIGNED_IMAGE_HEIGHT 240
-#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
-#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
-#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
-#define UNALIGNED_IMAGE "/data/local/tmp/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.buffer.get() +
-                                              mAlignedImage.width * mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.height,
-                                      mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
-                                      NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
-    JpegEncoderHelper encoder;
-    EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
-                                      mUnalignedImage.buffer.get() +
-                                              mUnalignedImage.width * mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.height,
-                                      mUnalignedImage.width, mUnalignedImage.width / 2,
-                                      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(), nullptr,
-                                      mSingleChannelImage.width, mSingleChannelImage.height,
-                                      mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
-    ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/tests/jpegr_test.cpp b/libs/ultrahdr/tests/jpegr_test.cpp
deleted file mode 100644
index 5fa758e..0000000
--- a/libs/ultrahdr/tests/jpegr_test.cpp
+++ /dev/null
@@ -1,2035 +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 <sys/time.h>
-#include <fstream>
-#include <iostream>
-
-#include <ultrahdr/gainmapmath.h>
-#include <ultrahdr/jpegr.h>
-#include <ultrahdr/jpegrutils.h>
-
-#include <gtest/gtest.h>
-#include <utils/Log.h>
-
-//#define DUMP_OUTPUT
-
-namespace android::ultrahdr {
-
-// resources used by unit tests
-const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
-const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
-const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
-const int kImageWidth = 1280;
-const int kImageHeight = 720;
-const int kQuality = 90;
-
-// Wrapper to describe the input type
-typedef enum {
-  YCbCr_p010 = 0,
-  YCbCr_420 = 1,
-} UhdrInputFormat;
-
-/**
- * Wrapper class for raw resource
- * Sample usage:
- *   UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
- *   rawImg.setImageColorGamut(colorGamut));
- *   rawImg.setImageStride(strideLuma, strideChroma); // optional
- *   rawImg.setChromaMode(false); // optional
- *   rawImg.allocateMemory();
- *   rawImg.loadRawResource(kYCbCrP010FileName);
- */
-class UhdrUnCompressedStructWrapper {
-public:
-  UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
-  ~UhdrUnCompressedStructWrapper() = default;
-
-  bool setChromaMode(bool isChromaContiguous);
-  bool setImageStride(int lumaStride, int chromaStride);
-  bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
-  bool allocateMemory();
-  bool loadRawResource(const char* fileName);
-  jr_uncompressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mLumaData;
-  std::unique_ptr<uint8_t[]> mChromaData;
-  jpegr_uncompressed_struct mImg;
-  UhdrInputFormat mFormat;
-  bool mIsChromaContiguous;
-};
-
-/**
- * Wrapper class for compressed resource
- * Sample usage:
- *   UhdrCompressedStructWrapper jpgImg(width, height);
- *   rawImg.allocateMemory();
- */
-class UhdrCompressedStructWrapper {
-public:
-  UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
-  ~UhdrCompressedStructWrapper() = default;
-
-  bool allocateMemory();
-  jr_compressed_ptr getImageHandle();
-
-private:
-  std::unique_ptr<uint8_t[]> mData;
-  jpegr_compressed_struct mImg{};
-  uint32_t mWidth;
-  uint32_t mHeight;
-};
-
-UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
-                                                             UhdrInputFormat format) {
-  mImg.data = nullptr;
-  mImg.width = width;
-  mImg.height = height;
-  mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-  mImg.chroma_data = nullptr;
-  mImg.luma_stride = 0;
-  mImg.chroma_stride = 0;
-  mFormat = format;
-  mIsChromaContiguous = true;
-}
-
-bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mIsChromaContiguous = isChromaContiguous;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  if (lumaStride != 0) {
-    if (lumaStride < mImg.width) {
-      std::cerr << "Bad luma stride received" << std::endl;
-      return false;
-    }
-    mImg.luma_stride = lumaStride;
-  }
-  if (chromaStride != 0) {
-    if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
-      std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
-      std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
-      return false;
-    }
-    mImg.chroma_stride = chromaStride;
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
-  if (mLumaData.get() != nullptr) {
-    std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
-    return false;
-  }
-  mImg.colorGamut = colorGamut;
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::allocateMemory() {
-  if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
-      (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-  int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
-  int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
-  if (mIsChromaContiguous) {
-    chromaSize *= lumaStride;
-  } else {
-    if (mImg.chroma_stride == 0) {
-      std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-      return false;
-    }
-    if (mFormat == YCbCr_p010) {
-      chromaSize *= mImg.chroma_stride;
-    } else {
-      chromaSize *= (mImg.chroma_stride * 2);
-    }
-  }
-  if (mIsChromaContiguous) {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
-    mImg.data = mLumaData.get();
-    mImg.chroma_data = nullptr;
-  } else {
-    mLumaData = std::make_unique<uint8_t[]>(lumaSize);
-    mImg.data = mLumaData.get();
-    mChromaData = std::make_unique<uint8_t[]>(chromaSize);
-    mImg.chroma_data = mChromaData.get();
-  }
-  return true;
-}
-
-bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
-  if (!mImg.data) {
-    std::cerr << "memory is not allocated, read not possible" << std::endl;
-    return false;
-  }
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    int bpp = mFormat == YCbCr_p010 ? 2 : 1;
-    int size = ifd.tellg();
-    int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
-    if (size < length) {
-      std::cerr << "requested to read " << length << " bytes from file : " << fileName
-                << ", file contains only " << length << " bytes" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
-    char* mem = static_cast<char*>(mImg.data);
-    for (int i = 0; i < mImg.height; i++) {
-      ifd.read(mem, mImg.width * bpp);
-      mem += lumaStride * bpp;
-    }
-    if (!mIsChromaContiguous) {
-      mem = static_cast<char*>(mImg.chroma_data);
-    }
-    int chromaStride;
-    if (mIsChromaContiguous) {
-      chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
-    } else {
-      if (mFormat == YCbCr_p010) {
-        chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
-      } else {
-        chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
-      }
-    }
-    if (mFormat == YCbCr_p010) {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, mImg.width * 2);
-        mem += chromaStride * 2;
-      }
-    } else {
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-      for (int i = 0; i < mImg.height / 2; i++) {
-        ifd.read(mem, (mImg.width / 2));
-        mem += chromaStride;
-      }
-    }
-    return true;
-  }
-  std::cerr << "unable to open file : " << fileName << std::endl;
-  return false;
-}
-
-jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
-  mWidth = width;
-  mHeight = height;
-}
-
-bool UhdrCompressedStructWrapper::allocateMemory() {
-  if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
-    std::cerr << "Object in bad state, mem alloc failed" << std::endl;
-    return false;
-  }
-  int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
-  mData = std::make_unique<uint8_t[]>(maxLength);
-  mImg.data = mData.get();
-  mImg.length = 0;
-  mImg.maxLength = maxLength;
-  return true;
-}
-
-jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
-  return &mImg;
-}
-
-static bool writeFile(const char* filename, void*& result, int length) {
-  std::ofstream ofd(filename, std::ios::binary);
-  if (ofd.is_open()) {
-    ofd.write(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to write to file : " << filename << std::endl;
-  return false;
-}
-
-static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
-  std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
-  if (ifd.good()) {
-    length = ifd.tellg();
-    if (length > maxLength) {
-      std::cerr << "not enough space to read file" << std::endl;
-      return false;
-    }
-    ifd.seekg(0, std::ios::beg);
-    ifd.read(static_cast<char*>(result), length);
-    return true;
-  }
-  std::cerr << "unable to read file : " << fileName << std::endl;
-  return false;
-}
-
-void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
-  std::vector<uint8_t> iccData(0);
-  std::vector<uint8_t> exifData(0);
-  jpegr_info_struct info{0, 0, &iccData, &exifData};
-  JpegR jpegHdr;
-  ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
-  ASSERT_EQ(kImageWidth, info.width);
-  ASSERT_EQ(kImageHeight, info.height);
-  size_t outSize = info.width * info.height * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  jpegr_uncompressed_struct destImage{};
-  destImage.data = data.get();
-  ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
-  ASSERT_EQ(kImageWidth, destImage.width);
-  ASSERT_EQ(kImageHeight, destImage.height);
-#ifdef DUMP_OUTPUT
-  if (!writeFile(outFileName, destImage.data, outSize)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-}
-
-// ============================================================================
-// Unit Tests
-// ============================================================================
-
-// Test Encode API-0 invalid arguments
-TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-1 invalid arguments */
-TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), -1, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), 101, nullptr),
-              OK)
-            << "fail, API allows bad jpeg quality factor";
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
-                                  nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle(), kQuality, nullptr),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-2 invalid arguments */
-TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-
-  // test 420 input
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-
-    UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
-                                  jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr 420 image";
-  }
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-    UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    auto rawImg420 = rawImg2.getImageHandle();
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad 420 color gamut";
-
-    rawImg420->width = kWidth - 1;
-    rawImg420->height = kHeight;
-    rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = 0;
-    rawImg420->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride for 420";
-
-    rawImg420->width = kWidth;
-    rawImg420->height = kHeight;
-    rawImg420->luma_stride = 0;
-    rawImg420->chroma_data = rawImgP010->data;
-    rawImg420->chroma_stride = kWidth / 2 - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride for 420";
-  }
-}
-
-/* Test Encode API-3 invalid arguments */
-TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test quality factor and transfer function
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(
-                                          ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  static_cast<ultrahdr_transfer_function>(-10),
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad hdr transfer function";
-  }
-
-  // test dest
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
-              OK)
-            << "fail, API allows nullptr dest";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr dest";
-  }
-
-  // test compressed image
-  {
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-    UhdrCompressedStructWrapper jpgImg2(16, 16);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr for compressed image";
-  }
-
-  // test p010 input
-  {
-    ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-
-    UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows nullptr p010 image";
-  }
-
-  {
-    const int kWidth = 32, kHeight = 32;
-    UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-    ASSERT_TRUE(rawImg.allocateMemory());
-    auto rawImgP010 = rawImg.getImageHandle();
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut =
-            static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad p010 color gamut";
-
-    rawImgP010->width = kWidth - 1;
-    rawImgP010->height = kHeight;
-    rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight - 1;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = 0;
-    rawImgP010->height = kHeight;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image width";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = 0;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad image height";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad luma stride";
-
-    rawImgP010->width = kWidth;
-    rawImgP010->height = kHeight;
-    rawImgP010->luma_stride = kWidth + 64;
-    rawImgP010->chroma_data = rawImgP010->data;
-    rawImgP010->chroma_stride = kWidth - 2;
-    ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg.getImageHandle()),
-              OK)
-            << "fail, API allows bad chroma stride";
-  }
-}
-
-/* Test Encode API-4 invalid arguments */
-TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgImg2(16, 16);
-  JpegR uHdrLib;
-
-  // test dest
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
-            OK)
-          << "fail, API allows nullptr dest";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg2.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr dest";
-
-  // test primary image
-  ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr primary image";
-
-  // test gain map
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows nullptr gain map image";
-
-  // test metadata
-  ultrahdr_metadata_struct good_metadata;
-  good_metadata.version = "1.0";
-  good_metadata.minContentBoost = 1.0f;
-  good_metadata.maxContentBoost = 2.0f;
-  good_metadata.gamma = 1.0f;
-  good_metadata.offsetSdr = 0.0f;
-  good_metadata.offsetHdr = 0.0f;
-  good_metadata.hdrCapacityMin = 1.0f;
-  good_metadata.hdrCapacityMax = 2.0f;
-
-  ultrahdr_metadata_struct metadata = good_metadata;
-  metadata.version = "1.1";
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata version";
-
-  metadata = good_metadata;
-  metadata.minContentBoost = 3.0f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata content boost";
-
-  metadata = good_metadata;
-  metadata.gamma = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata gamma";
-
-  metadata = good_metadata;
-  metadata.offsetSdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset sdr";
-
-  metadata = good_metadata;
-  metadata.offsetHdr = -0.1f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata offset hdr";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMax = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity max";
-
-  metadata = good_metadata;
-  metadata.hdrCapacityMin = 0.5f;
-  ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
-                                jpgImg.getImageHandle()),
-            OK)
-          << "fail, API allows bad metadata hdr capacity min";
-}
-
-/* Test Decode API invalid arguments */
-TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
-  JpegR uHdrLib;
-
-  UhdrCompressedStructWrapper jpgImg(16, 16);
-  jpegr_uncompressed_struct destImage{};
-  size_t outSize = 16 * 16 * 8;
-  std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
-  destImage.data = data.get();
-
-  // test jpegr image
-  ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for jpegr img";
-  ASSERT_TRUE(jpgImg.allocateMemory());
-
-  // test dest image
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = nullptr;
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
-          << "fail, API allows nullptr for dest";
-  destImage.data = data.get();
-
-  // test max display boost
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
-          << "fail, API allows invalid max display boost";
-
-  // test output format
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(-1)),
-            OK)
-          << "fail, API allows invalid output format";
-  ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
-                                static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
-            OK)
-          << "fail, API allows invalid output format";
-}
-
-TEST(JpegRTest, writeXmpThenRead) {
-  ultrahdr_metadata_struct metadata_expected;
-  metadata_expected.version = "1.0";
-  metadata_expected.maxContentBoost = 1.25f;
-  metadata_expected.minContentBoost = 0.75f;
-  metadata_expected.gamma = 1.0f;
-  metadata_expected.offsetSdr = 0.0f;
-  metadata_expected.offsetHdr = 0.0f;
-  metadata_expected.hdrCapacityMin = 1.0f;
-  metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
-  const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
-  const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
-
-  std::string xmp = generateXmpForSecondaryImage(metadata_expected);
-
-  std::vector<uint8_t> xmpData;
-  xmpData.reserve(nameSpaceLength + xmp.size());
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
-                 reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
-  xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
-                 reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
-
-  ultrahdr_metadata_struct metadata_read;
-  EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
-  EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
-  EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr);
-  EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin);
-  EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
-}
-
-class JpegRAPIEncodeAndDecodeTest
-      : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
-public:
-  JpegRAPIEncodeAndDecodeTest()
-        : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
-
-  const ultrahdr_color_gamut mP010ColorGamut;
-  const ultrahdr_color_gamut mYuv420ColorGamut;
-};
-
-/* Test Encode API-0 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
-  // reference encode
-  UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImg.allocateMemory());
-  ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
-    ASSERT_TRUE(rawImg2.setChromaMode(false));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
-    ASSERT_TRUE(rawImg2.allocateMemory());
-    ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
-}
-
-/* Test Encode API-1 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle(), kQuality, nullptr),
-            OK);
-  // encode with luma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr p010
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set but no chroma ptr 420
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle(), kQuality, nullptr),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
-}
-
-/* Test Encode API-2 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
-    ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
-    ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2420.setChromaMode(false));
-    ASSERT_TRUE(rawImg2420.allocateMemory());
-    ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
-}
-
-/* Test Encode API-3 and Decode */
-TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgImg.allocateMemory());
-  UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
-  ASSERT_TRUE(jpgSdr.allocateMemory());
-  auto sdr = jpgSdr.getImageHandle();
-  ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
-  JpegR uHdrLib;
-  ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
-                                ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                jpgImg.getImageHandle()),
-            OK);
-  // encode with luma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with chroma stride set
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
-    ASSERT_TRUE(rawImg2P010.setChromaMode(false));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-  // encode with luma and chroma stride set and no chroma ptr
-  {
-    UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
-    ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
-    ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
-    ASSERT_TRUE(rawImg2P010.allocateMemory());
-    ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
-    UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
-    ASSERT_TRUE(jpgImg2.allocateMemory());
-    ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
-                                  ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
-                                  jpgImg2.getImageHandle()),
-              OK);
-    auto jpg1 = jpgImg.getImageHandle();
-    auto jpg2 = jpgImg2.getImageHandle();
-    ASSERT_EQ(jpg1->length, jpg2->length);
-    ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
-  }
-
-  auto jpg1 = jpgImg.getImageHandle();
-
-#ifdef DUMP_OUTPUT
-  if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
-    std::cerr << "unable to write output file" << std::endl;
-  }
-#endif
-
-  ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-        JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
-        ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100),
-                           ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
-                                             ULTRAHDR_COLORGAMUT_BT2100)));
-
-// ============================================================================
-// Profiling
-// ============================================================================
-
-class Profiler {
-public:
-  void timerStart() { gettimeofday(&mStartingTime, nullptr); }
-
-  void timerStop() { gettimeofday(&mEndingTime, nullptr); }
-
-  int64_t elapsedTime() {
-    struct timeval elapsedMicroseconds;
-    elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
-    elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
-    return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
-  }
-
-private:
-  struct timeval mStartingTime;
-  struct timeval mEndingTime;
-};
-
-class JpegRBenchmark : public JpegR {
-public:
-  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::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);
-  Profiler profileGenerateMap;
-  profileGenerateMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    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);
-  }
-  profileGenerateMap.timerStop();
-  ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
-                                           ultrahdr_metadata_ptr metadata,
-                                           jr_uncompressed_ptr dest) {
-  Profiler profileRecMap;
-  profileRecMap.timerStart();
-  for (auto i = 0; i < kProfileCount; i++) {
-    ASSERT_EQ(OK,
-              applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
-                           metadata->maxContentBoost /* displayBoost */, dest));
-  }
-  profileRecMap.timerStop();
-  ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
-        profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
-}
-
-TEST(JpegRTest, ProfileGainMapFuncs) {
-  UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
-  ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
-  ASSERT_TRUE(rawImgP010.allocateMemory());
-  ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
-  UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
-  ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
-  ASSERT_TRUE(rawImg420.allocateMemory());
-  ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
-  ultrahdr_metadata_struct metadata = {.version = "1.0"};
-  jpegr_uncompressed_struct map = {.data = NULL,
-                                   .width = 0,
-                                   .height = 0,
-                                   .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-  {
-    auto rawImg = rawImgP010.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride;
-    }
-  }
-  {
-    auto rawImg = rawImg420.getImageHandle();
-    if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
-    if (!rawImg->chroma_data) {
-      uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
-      rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
-      rawImg->chroma_stride = rawImg->luma_stride / 2;
-    }
-  }
-
-  JpegRBenchmark benchmark;
-  ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
-                                                             rawImgP010.getImageHandle(), &metadata,
-                                                             &map));
-
-  const int dstSize = kImageWidth * kImageWidth * 4;
-  auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
-  jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
-                                    .width = 0,
-                                    .height = 0,
-                                    .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
-
-  ASSERT_NO_FATAL_FAILURE(
-          benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
-}
-
-} // namespace android::ultrahdr
diff --git a/libs/vibrator/Android.bp b/libs/vibrator/Android.bp
index 2af51a7..d3b3a73 100644
--- a/libs/vibrator/Android.bp
+++ b/libs/vibrator/Android.bp
@@ -24,6 +24,10 @@
 cc_defaults {
     name: "libvibrator_defaults",
 
+    defaults: [
+        "aconfig_lib_cc_shared_link.defaults",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
@@ -50,9 +54,11 @@
         "libbinder",
         "liblog",
         "libutils",
+        "server_configurable_flags",
     ],
 
     whole_static_libs: [
+        "android.os.vibrator.flags-aconfig-cc",
         "libvibratorutils",
     ],
 
@@ -79,8 +85,14 @@
     vendor_available: true,
     double_loadable: true,
 
+    static_libs: [
+        "android.os.vibrator.flags-aconfig-cc",
+    ],
+
     shared_libs: [
+        "liblog",
         "libutils",
+        "server_configurable_flags",
     ],
 
     srcs: [
@@ -89,6 +101,7 @@
 
     visibility: [
         "//frameworks/native/libs/vibrator",
+        "//frameworks/native/libs/vibrator/tests",
         "//frameworks/av/media/libeffects/hapticgenerator",
     ],
 }
diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp
index c97e496..cae2de2 100644
--- a/libs/vibrator/ExternalVibration.cpp
+++ b/libs/vibrator/ExternalVibration.cpp
@@ -93,8 +93,8 @@
                   externalVibrationScale.scaleLevel);
     }
 
-    return {/*level=*/scaleLevel, /*adaptiveScaleFactor=*/
-                      externalVibrationScale.adaptiveHapticsScale};
+    return os::HapticScale(scaleLevel, externalVibrationScale.scaleFactor,
+                           externalVibrationScale.adaptiveHapticsScale);
 }
 
 } // namespace os
diff --git a/libs/vibrator/ExternalVibrationUtils.cpp b/libs/vibrator/ExternalVibrationUtils.cpp
index 761ac1b..ca13afc 100644
--- a/libs/vibrator/ExternalVibrationUtils.cpp
+++ b/libs/vibrator/ExternalVibrationUtils.cpp
@@ -13,10 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#define LOG_TAG "ExternalVibrationUtils"
+
 #include <cstring>
 
+#include <android_os_vibrator.h>
+
+#include <algorithm>
 #include <math.h>
 
+#include <log/log.h>
 #include <vibrator/ExternalVibrationUtils.h>
 
 namespace android::os {
@@ -25,8 +31,10 @@
 static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
 static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
 static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
+static constexpr float SCALE_GAMMA = 0.65f; // Same as VibrationEffect.SCALE_GAMMA
+static constexpr float SCALE_LEVEL_GAIN = 1.4f; // Same as VibrationConfig.DEFAULT_SCALE_LEVEL_GAIN
 
-float getHapticScaleGamma(HapticLevel level) {
+float getOldHapticScaleGamma(HapticLevel level) {
     switch (level) {
     case HapticLevel::VERY_LOW:
         return 2.0f;
@@ -41,7 +49,7 @@
     }
 }
 
-float getHapticMaxAmplitudeRatio(HapticLevel level) {
+float getOldHapticMaxAmplitudeRatio(HapticLevel level) {
     switch (level) {
     case HapticLevel::VERY_LOW:
         return HAPTIC_SCALE_VERY_LOW_RATIO;
@@ -56,6 +64,85 @@
     }
 }
 
+/* Same as VibrationScaler.getScaleFactor */
+float getHapticScaleFactor(HapticScale scale) {
+    if (android_os_vibrator_haptics_scale_v2_enabled()) {
+        if (scale.getScaleFactor() >= 0) {
+            // ExternalVibratorService provided the scale factor, use it.
+            return scale.getScaleFactor();
+        }
+
+        HapticLevel level = scale.getLevel();
+        switch (level) {
+            case HapticLevel::MUTE:
+                return 0.0f;
+            case HapticLevel::NONE:
+                return 1.0f;
+            default:
+                float scaleFactor = powf(SCALE_LEVEL_GAIN, static_cast<int32_t>(level));
+                if (scaleFactor <= 0) {
+                    ALOGE("Invalid scale factor %.2f for level %d, using fallback to 1.0",
+                          scaleFactor, static_cast<int32_t>(level));
+                    scaleFactor = 1.0f;
+                }
+                return scaleFactor;
+        }
+    }
+    // Same as VibrationScaler.SCALE_FACTOR_*
+    switch (scale.getLevel()) {
+        case HapticLevel::MUTE:
+            return 0.0f;
+        case HapticLevel::VERY_LOW:
+            return 0.6f;
+        case HapticLevel::LOW:
+            return 0.8f;
+        case HapticLevel::HIGH:
+            return 1.2f;
+        case HapticLevel::VERY_HIGH:
+            return 1.4f;
+        default:
+            return 1.0f;
+    }
+}
+
+float applyOldHapticScale(float value, float gamma, float maxAmplitudeRatio) {
+    float sign = value >= 0 ? 1.0 : -1.0;
+    return powf(fabsf(value / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
+                * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+}
+
+float applyNewHapticScale(float value, float scaleFactor) {
+    if (android_os_vibrator_haptics_scale_v2_enabled()) {
+        if (scaleFactor <= 1 || value == 0) {
+            return value * scaleFactor;
+        } else {
+            // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0.
+            return (value * scaleFactor) / (1 + (scaleFactor - 1) * value * value);
+        }
+    }
+    float scale = powf(scaleFactor, 1.0f / SCALE_GAMMA);
+    if (scaleFactor <= 1) {
+        // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
+        // Scale up requires a different curve to ensure the intensity will not become > 1.
+        return value * scale;
+    }
+
+    float sign = value >= 0 ? 1.0f : -1.0f;
+    float extraScale = powf(scaleFactor, 4.0f - scaleFactor);
+    float x = fabsf(value) * scale * extraScale;
+    float maxX = scale * extraScale; // scaled x for intensity == 1
+
+    float expX = expf(x);
+    float expMaxX = expf(maxX);
+
+    // Using f = tanh as the scale up function so the max value will converge.
+    // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
+    float a = (expMaxX + 1.0f) / (expMaxX - 1.0f);
+    float fx = (expX - 1.0f) / (expX + 1.0f);
+
+    return sign * std::clamp(a * fx, 0.0f, 1.0f);
+}
+
 void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
     if (scale.isScaleMute()) {
         memset(buffer, 0, length * sizeof(float));
@@ -65,18 +152,22 @@
         return;
     }
     HapticLevel hapticLevel = scale.getLevel();
+    float scaleFactor = getHapticScaleFactor(scale);
     float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
-    float gamma = getHapticScaleGamma(hapticLevel);
-    float maxAmplitudeRatio = getHapticMaxAmplitudeRatio(hapticLevel);
+    float oldGamma = getOldHapticScaleGamma(hapticLevel);
+    float oldMaxAmplitudeRatio = getOldHapticMaxAmplitudeRatio(hapticLevel);
 
     for (size_t i = 0; i < length; i++) {
         if (hapticLevel != HapticLevel::NONE) {
-            float sign = buffer[i] >= 0 ? 1.0 : -1.0;
-            buffer[i] = powf(fabsf(buffer[i] / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
-                        * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
+            if (android_os_vibrator_fix_audio_coupled_haptics_scaling() ||
+                android_os_vibrator_haptics_scale_v2_enabled()) {
+                buffer[i] = applyNewHapticScale(buffer[i], scaleFactor);
+            } else {
+                buffer[i] = applyOldHapticScale(buffer[i], oldGamma, oldMaxAmplitudeRatio);
+            }
         }
 
-        if (adaptiveScaleFactor != 1.0f) {
+        if (adaptiveScaleFactor >= 0 && adaptiveScaleFactor != 1.0f) {
             buffer[i] *= adaptiveScaleFactor;
         }
     }
diff --git a/libs/vibrator/TEST_MAPPING b/libs/vibrator/TEST_MAPPING
new file mode 100644
index 0000000..d782b43
--- /dev/null
+++ b/libs/vibrator/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "libvibrator_test"
+    }
+  ]
+}
diff --git a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
index d9a2b81..f0760fd 100644
--- a/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
+++ b/libs/vibrator/include/vibrator/ExternalVibrationUtils.h
@@ -17,9 +17,13 @@
 #ifndef ANDROID_EXTERNAL_VIBRATION_UTILS_H
 #define ANDROID_EXTERNAL_VIBRATION_UTILS_H
 
+#include <cstring>
+#include <sstream>
+#include <string>
+
 namespace android::os {
 
-enum class HapticLevel {
+enum class HapticLevel : int32_t {
     MUTE = -100,
     VERY_LOW = -2,
     LOW = -1,
@@ -31,32 +35,44 @@
 class HapticScale {
 private:
 HapticLevel mLevel = HapticLevel::NONE;
+float mScaleFactor = -1.0f; // undefined, use haptic level to define scale factor
 float mAdaptiveScaleFactor = 1.0f;
 
 public:
-constexpr HapticScale(HapticLevel level, float adaptiveScaleFactor)
-    : mLevel(level), mAdaptiveScaleFactor(adaptiveScaleFactor) {}
-constexpr HapticScale(HapticLevel level) : mLevel(level) {}
-constexpr HapticScale() {}
+    explicit HapticScale(HapticLevel level, float scaleFactor, float adaptiveScaleFactor)
+          : mLevel(level), mScaleFactor(scaleFactor), mAdaptiveScaleFactor(adaptiveScaleFactor) {}
+    explicit HapticScale(HapticLevel level) : mLevel(level) {}
+    constexpr HapticScale() {}
 
-HapticLevel getLevel() const { return mLevel; }
-float getAdaptiveScaleFactor() const { return mAdaptiveScaleFactor; }
+    HapticLevel getLevel() const { return mLevel; }
+    float getScaleFactor() const { return mScaleFactor; }
+    float getAdaptiveScaleFactor() const { return mAdaptiveScaleFactor; }
 
-bool operator==(const HapticScale& other) const {
-    return mLevel == other.mLevel && mAdaptiveScaleFactor == other.mAdaptiveScaleFactor;
-}
+    bool operator==(const HapticScale& other) const {
+        return mLevel == other.mLevel && mScaleFactor == other.mScaleFactor &&
+                mAdaptiveScaleFactor == other.mAdaptiveScaleFactor;
+    }
 
 bool isScaleNone() const {
-    return mLevel == HapticLevel::NONE && mAdaptiveScaleFactor == 1.0f;
+    return (mLevel == HapticLevel::NONE || mScaleFactor == 1.0f) && mAdaptiveScaleFactor == 1.0f;
 }
 
 bool isScaleMute() const {
-    return mLevel == HapticLevel::MUTE;
+    return mLevel == HapticLevel::MUTE || mScaleFactor == 0 || mAdaptiveScaleFactor == 0;
 }
 
-static HapticScale mute() {
-    return {/*level=*/os::HapticLevel::MUTE};
+std::string toString() const {
+    std::ostringstream os;
+    os << "HapticScale { level: " << static_cast<int>(mLevel);
+    os << ", scaleFactor: " << mScaleFactor;
+    os << ", adaptiveScaleFactor: " << mAdaptiveScaleFactor;
+    os << "}";
+    return os.str();
 }
+
+static HapticScale mute() { return os::HapticScale(os::HapticLevel::MUTE); }
+
+static HapticScale none() { return os::HapticScale(os::HapticLevel::NONE); }
 };
 
 bool isValidHapticScale(HapticScale scale);
diff --git a/libs/vibrator/tests/Android.bp b/libs/vibrator/tests/Android.bp
new file mode 100644
index 0000000..2921a62
--- /dev/null
+++ b/libs/vibrator/tests/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_team: "trendy_team_haptics_framework",
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_test {
+    name: "libvibrator_test",
+    test_suites: ["general-tests"],
+    defaults: [
+        "aconfig_lib_cc_shared_link.defaults",
+    ],
+    srcs: [
+        "ExternalVibrationTest.cpp",
+        "ExternalVibrationUtilsTest.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wextra",
+    ],
+    static_libs: [
+        "android.os.vibrator.flags-aconfig-cc",
+        "libflagtest",
+        "libgtest",
+        "liblog",
+        "libvibrator",
+        "libvibratorutils",
+    ],
+    shared_libs: [
+        "libbase",
+        "libbinder",
+        "libutils",
+        "server_configurable_flags",
+    ],
+}
diff --git a/libs/vibrator/tests/ExternalVibrationTest.cpp b/libs/vibrator/tests/ExternalVibrationTest.cpp
new file mode 100644
index 0000000..4133836
--- /dev/null
+++ b/libs/vibrator/tests/ExternalVibrationTest.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <binder/Parcel.h>
+#include <gtest/gtest.h>
+#include <vibrator/ExternalVibration.h>
+
+using namespace android;
+using namespace testing;
+
+using HapticLevel = os::HapticLevel;
+using ScaleLevel = os::ExternalVibrationScale::ScaleLevel;
+
+class TestVibrationController : public os::IExternalVibrationController {
+public:
+    explicit TestVibrationController() {}
+    IBinder *onAsBinder() override { return nullptr; }
+    binder::Status mute(/*out*/ bool *ret) override {
+        *ret = false;
+        return binder::Status::ok();
+    };
+    binder::Status unmute(/*out*/ bool *ret) override {
+        *ret = false;
+        return binder::Status::ok();
+    };
+};
+
+class ExternalVibrationTest : public Test {
+protected:
+    HapticLevel toHapticLevel(ScaleLevel level) {
+        os::ExternalVibrationScale externalVibrationScale;
+        externalVibrationScale.scaleLevel = level;
+        os::HapticScale hapticScale =
+                os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
+        return hapticScale.getLevel();
+    }
+};
+
+TEST_F(ExternalVibrationTest, TestReadAndWriteToParcel) {
+    int32_t uid = 1;
+    std::string pkg("package.name");
+    audio_attributes_t originalAttrs;
+    originalAttrs.content_type = AUDIO_CONTENT_TYPE_SONIFICATION;
+    originalAttrs.usage = AUDIO_USAGE_ASSISTANCE_SONIFICATION;
+    originalAttrs.source = AUDIO_SOURCE_VOICE_COMMUNICATION;
+    originalAttrs.flags = AUDIO_FLAG_BYPASS_MUTE;
+
+    sp<TestVibrationController> vibrationController = new TestVibrationController();
+    ASSERT_NE(vibrationController, nullptr);
+
+    sp<os::ExternalVibration> original =
+            new os::ExternalVibration(uid, pkg, originalAttrs, vibrationController);
+    ASSERT_NE(original, nullptr);
+
+    EXPECT_EQ(original->getUid(), uid);
+    EXPECT_EQ(original->getPackage(), pkg);
+    EXPECT_EQ(original->getAudioAttributes().content_type, originalAttrs.content_type);
+    EXPECT_EQ(original->getAudioAttributes().usage, originalAttrs.usage);
+    EXPECT_EQ(original->getAudioAttributes().source, originalAttrs.source);
+    EXPECT_EQ(original->getAudioAttributes().flags, originalAttrs.flags);
+    EXPECT_EQ(original->getController(), vibrationController);
+
+    audio_attributes_t defaultAttrs;
+    defaultAttrs.content_type = AUDIO_CONTENT_TYPE_UNKNOWN;
+    defaultAttrs.usage = AUDIO_USAGE_UNKNOWN;
+    defaultAttrs.source = AUDIO_SOURCE_DEFAULT;
+    defaultAttrs.flags = AUDIO_FLAG_NONE;
+
+    sp<os::ExternalVibration> parceled =
+            new os::ExternalVibration(0, std::string(""), defaultAttrs, nullptr);
+    ASSERT_NE(parceled, nullptr);
+
+    Parcel parcel;
+    original->writeToParcel(&parcel);
+    parcel.setDataPosition(0);
+    parceled->readFromParcel(&parcel);
+
+    EXPECT_EQ(parceled->getUid(), uid);
+    EXPECT_EQ(parceled->getPackage(), pkg);
+    EXPECT_EQ(parceled->getAudioAttributes().content_type, originalAttrs.content_type);
+    EXPECT_EQ(parceled->getAudioAttributes().usage, originalAttrs.usage);
+    EXPECT_EQ(parceled->getAudioAttributes().source, originalAttrs.source);
+    EXPECT_EQ(parceled->getAudioAttributes().flags, originalAttrs.flags);
+    // TestVibrationController does not implement onAsBinder, skip controller parcel in this test.
+}
+
+TEST_F(ExternalVibrationTest, TestExternalVibrationScaleToHapticScale) {
+    os::ExternalVibrationScale externalVibrationScale;
+    externalVibrationScale.scaleLevel = ScaleLevel::SCALE_HIGH;
+    externalVibrationScale.scaleFactor = 0.5f;
+    externalVibrationScale.adaptiveHapticsScale = 0.8f;
+
+    os::HapticScale hapticScale =
+            os::ExternalVibration::externalVibrationScaleToHapticScale(externalVibrationScale);
+
+    // Check scale factors are forwarded.
+    EXPECT_EQ(hapticScale.getLevel(), HapticLevel::HIGH);
+    EXPECT_EQ(hapticScale.getScaleFactor(), 0.5f);
+    EXPECT_EQ(hapticScale.getAdaptiveScaleFactor(), 0.8f);
+
+    // Check conversion for all levels.
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_MUTE), HapticLevel::MUTE);
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_LOW), HapticLevel::VERY_LOW);
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_LOW), HapticLevel::LOW);
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_NONE), HapticLevel::NONE);
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_HIGH), HapticLevel::HIGH);
+    EXPECT_EQ(toHapticLevel(ScaleLevel::SCALE_VERY_HIGH), HapticLevel::VERY_HIGH);
+}
diff --git a/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
new file mode 100644
index 0000000..9369f80
--- /dev/null
+++ b/libs/vibrator/tests/ExternalVibrationUtilsTest.cpp
@@ -0,0 +1,380 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *            http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android_os_vibrator.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <vibrator/ExternalVibrationUtils.h>
+
+#include "test_utils.h"
+
+#define FLAG_NS android::os::vibrator
+
+using namespace android;
+using namespace testing;
+
+using HapticScale = os::HapticScale;
+using HapticLevel = os::HapticLevel;
+
+static constexpr float TEST_TOLERANCE = 1e-2f;
+static constexpr size_t TEST_BUFFER_LENGTH = 4;
+static float TEST_BUFFER[TEST_BUFFER_LENGTH] = { 1, -1, 0.5f, -0.2f };
+
+class ExternalVibrationUtilsTest : public Test {
+public:
+    void SetUp() override {
+        std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(mBuffer));
+    }
+
+protected:
+    void scaleBuffer(HapticLevel hapticLevel) {
+        scaleBuffer(HapticScale(hapticLevel));
+    }
+
+    void scaleBuffer(HapticLevel hapticLevel, float adaptiveScaleFactor) {
+        scaleBuffer(hapticLevel, adaptiveScaleFactor, 0 /* limit */);
+    }
+
+    void scaleBuffer(HapticLevel hapticLevel, float adaptiveScaleFactor, float limit) {
+        scaleBuffer(HapticScale(hapticLevel, -1 /* scaleFactor */, adaptiveScaleFactor), limit);
+    }
+
+    void scaleBuffer(HapticScale hapticScale) {
+        scaleBuffer(hapticScale, 0 /* limit */);
+    }
+
+    void scaleBuffer(HapticScale hapticScale, float limit) {
+        std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(mBuffer));
+        os::scaleHapticData(&mBuffer[0], TEST_BUFFER_LENGTH, hapticScale, limit);
+    }
+
+    float mBuffer[TEST_BUFFER_LENGTH];
+};
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestLegacyScaleMute,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expected), std::end(expected), 0);
+
+    scaleBuffer(HapticLevel::MUTE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestFixedScaleMute,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expected), std::end(expected), 0);
+
+    scaleBuffer(HapticLevel::MUTE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2Mute,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expected), std::end(expected), 0);
+
+    scaleBuffer(HapticLevel::MUTE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestLegacyScaleNone,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(expected));
+
+    scaleBuffer(HapticLevel::NONE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestFixedScaleNone,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(expected));
+
+    scaleBuffer(HapticLevel::NONE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2None,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expected[TEST_BUFFER_LENGTH];
+    std::copy(std::begin(TEST_BUFFER), std::end(TEST_BUFFER), std::begin(expected));
+
+    scaleBuffer(HapticLevel::NONE);
+    EXPECT_FLOATS_NEARLY_EQ(expected, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestLegacyScaleToHapticLevel,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.84f, -0.66f };
+    scaleBuffer(HapticLevel::VERY_HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.7f, -0.44f };
+    scaleBuffer(HapticLevel::HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.75f, -0.75f, 0.26f, -0.06f };
+    scaleBuffer(HapticLevel::LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.66f, -0.66f, 0.16f, -0.02f };
+    scaleBuffer(HapticLevel::VERY_LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestFixedScaleToHapticLevel,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.79f, -0.39f };
+    scaleBuffer(HapticLevel::VERY_HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.62f, -0.27f };
+    scaleBuffer(HapticLevel::HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.70f, -0.70f, 0.35f, -0.14f };
+    scaleBuffer(HapticLevel::LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.45f, -0.45f, 0.22f, -0.09f };
+    scaleBuffer(HapticLevel::VERY_LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2ToHapticLevel,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.8f, -0.38f };
+    scaleBuffer(HapticLevel::VERY_HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.63f, -0.27f };
+    scaleBuffer(HapticLevel::HIGH);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.71f, -0.71f, 0.35f, -0.14f };
+    scaleBuffer(HapticLevel::LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.51f, -0.51f, 0.25f, -0.1f };
+    scaleBuffer(HapticLevel::VERY_LOW);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2ToScaleFactorUndefinedUsesHapticLevel,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    constexpr float adaptiveScaleNone = 1.0f;
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.8f, -0.38f};
+    scaleBuffer(HapticScale(HapticLevel::VERY_HIGH, -1.0f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestScaleV2ToScaleFactorIgnoresLevel,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    constexpr float adaptiveScaleNone = 1.0f;
+
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 1, -1, 1, -0.55f };
+    scaleBuffer(HapticScale(HapticLevel::LOW, 3.0f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1, -1, 0.66f, -0.29f };
+    scaleBuffer(HapticScale(HapticLevel::LOW, 1.5f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.8f, -0.8f, 0.4f, -0.16f };
+    scaleBuffer(HapticScale(HapticLevel::HIGH, 0.8f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.4f, -0.4f, 0.2f, -0.08f };
+    scaleBuffer(HapticScale(HapticLevel::HIGH, 0.4f /* scaleFactor */, adaptiveScaleNone));
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIsIgnoredLegacyScale,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.79f, -0.39f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterLegacyScale,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale down
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.13f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale up
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1.5f, -1.5f, 1.06f, -0.67f };
+    scaleBuffer(HapticLevel::HIGH, 1.5f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale down
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.45f, -0.45f, 0.15f, -0.04f };
+    scaleBuffer(HapticLevel::LOW, 0.6f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale up
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 1.33f, -1.33f, 0.33f, -0.05f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIgnoredFixedScale,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.79f, -0.39f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterFixedScale,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale down
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.16f, -0.07f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale up
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1.5f, -1.5f, 0.93f, -0.41f };
+    scaleBuffer(HapticLevel::HIGH, 1.5f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale down
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.42f, -0.42f, 0.21f, -0.08f };
+    scaleBuffer(HapticLevel::LOW, 0.6f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale up
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 0.91f, -0.91f, 0.45f, -0.18f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestAdaptiveScaleFactorUndefinedIgnoredScaleV2,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = {1, -1, 0.8f, -0.38f};
+    scaleBuffer(HapticLevel::VERY_HIGH, -1.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestAdaptiveScaleFactorAppliedAfterScaleV2,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Adaptive scale mutes vibration
+    float expectedMuted[TEST_BUFFER_LENGTH];
+    std::fill(std::begin(expectedMuted), std::end(expectedMuted), 0);
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.0f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedMuted, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale down
+    float expectedVeryHigh[TEST_BUFFER_LENGTH] = { 0.2, -0.2, 0.15f, -0.07f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale up then adaptive scale up
+    float expectedHigh[TEST_BUFFER_LENGTH] = { 1.5f, -1.5f, 0.95f, -0.41f };
+    scaleBuffer(HapticLevel::HIGH, 1.5f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale down
+    float expectedLow[TEST_BUFFER_LENGTH] = { 0.42f, -0.42f, 0.21f, -0.08f };
+    scaleBuffer(HapticLevel::LOW, 0.6f /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Haptic level scale down then adaptive scale up
+    float expectedVeryLow[TEST_BUFFER_LENGTH] = { 1.02f, -1.02f, 0.51f, -0.2f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestLimitAppliedAfterLegacyScale,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling),
+                                          ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Scaled = { 0.2, -0.2, 0.16f, -0.13f };
+    float expectedClippedVeryHigh[TEST_BUFFER_LENGTH] = { 0.15f, -0.15f, 0.15f, -0.13f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */, 0.15f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Scaled = { 1, -1, 0.5f, -0.2f };
+    float expectedClippedVeryLow[TEST_BUFFER_LENGTH] = { 0.7f, -0.7f, 0.33f, -0.05f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */, 0.7f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(ExternalVibrationUtilsTest, TestLimitAppliedAfterFixedScale,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, fix_audio_coupled_haptics_scaling)),
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Scaled = { 0.2, -0.2, 0.16f, -0.13f };
+    float expectedClippedVeryHigh[TEST_BUFFER_LENGTH] = { 0.15f, -0.15f, 0.15f, -0.07f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */, 0.15f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Scaled = { 1, -1, 0.5f, -0.2f };
+    float expectedClippedVeryLow[TEST_BUFFER_LENGTH] = { 0.7f, -0.7f, 0.45f, -0.18f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */, 0.7f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
+
+TEST_F_WITH_FLAGS(
+        ExternalVibrationUtilsTest, TestLimitAppliedAfterScaleV2,
+        // Value of fix_audio_coupled_haptics_scaling is not important, should work with either
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(FLAG_NS, haptics_scale_v2_enabled))) {
+    // Scaled = { 0.2, -0.2, 0.15f, -0.07f };
+    float expectedClippedVeryHigh[TEST_BUFFER_LENGTH] = { 0.15f, -0.15f, 0.15f, -0.07f };
+    scaleBuffer(HapticLevel::VERY_HIGH, 0.2f /* adaptiveScaleFactor */, 0.15f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryHigh, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+
+    // Scaled = { 1.02f, -1.02f, 0.51f, -0.2f }
+    float expectedClippedVeryLow[TEST_BUFFER_LENGTH] = { 0.7f, -0.7f, 0.51f, -0.2f };
+    scaleBuffer(HapticLevel::VERY_LOW, 2 /* adaptiveScaleFactor */, 0.7f /* limit */);
+    EXPECT_FLOATS_NEARLY_EQ(expectedClippedVeryLow, mBuffer, TEST_BUFFER_LENGTH, TEST_TOLERANCE);
+}
diff --git a/libs/vibrator/tests/test_utils.h b/libs/vibrator/tests/test_utils.h
new file mode 100644
index 0000000..f491ea1
--- /dev/null
+++ b/libs/vibrator/tests/test_utils.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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 LIBVIBRATOR_TEST_UTILS_H
+#define LIBVIBRATOR_TEST_UTILS_H
+
+#include <gtest/gtest.h>
+
+#if !defined(EXPECT_FLOATS_NEARLY_EQ)
+#define EXPECT_FLOATS_NEARLY_EQ(expected, actual, length, epsilon)              \
+        for (size_t i = 0; i < length; i++) {                                   \
+            EXPECT_NEAR(expected[i], actual[i], epsilon) << " at Index: " << i; \
+        }
+#else
+#error Macro EXPECT_FLOATS_NEARLY_EQ already defined
+#endif
+
+#endif //LIBVIBRATOR_TEST_UTILS_H
diff --git a/opengl/Android.bp b/opengl/Android.bp
index 4454f36..37dc931 100644
--- a/opengl/Android.bp
+++ b/opengl/Android.bp
@@ -30,6 +30,10 @@
     to: "",
     srcs: ["include/EGL/**/*.h"],
     license: "include/EGL/NOTICE",
+    // eglext.h is not self-contained. Safe to skip C-compat verification
+    // though since upstream also cares about C compatibility, and the header is
+    // auto-generated anyway.
+    skip_verification: true,
 }
 
 ndk_headers {
@@ -38,6 +42,10 @@
     to: "",
     srcs: ["include/GLES/**/*.h"],
     license: "include/GLES/NOTICE",
+    // glext.h is not self-contained. Safe to skip C-compat verification
+    // though since upstream also cares about C compatibility, and the header is
+    // auto-generated anyway.
+    skip_verification: true,
 }
 
 ndk_headers {
@@ -46,6 +54,10 @@
     to: "",
     srcs: ["include/GLES2/**/*.h"],
     license: "include/GLES2/NOTICE",
+    // gl2ext.h is not self-contained. Safe to skip C-compat verification
+    // though since upstream also cares about C compatibility, and the header is
+    // auto-generated anyway.
+    skip_verification: true,
 }
 
 ndk_headers {
diff --git a/opengl/OWNERS b/opengl/OWNERS
index 3d60a1d..645a578 100644
--- a/opengl/OWNERS
+++ b/opengl/OWNERS
@@ -2,5 +2,4 @@
 cnorthrop@google.com
 ianelliott@google.com
 jessehall@google.com
-lpy@google.com
-vantablack@google.com
+tomnom@google.com
diff --git a/opengl/include/EGL/egl.h b/opengl/include/EGL/egl.h
index c9e8b7c..782b6d9 100644
--- a/opengl/include/EGL/egl.h
+++ b/opengl/include/EGL/egl.h
@@ -6,39 +6,24 @@
 #endif
 
 /*
-** Copyright (c) 2013-2017 The Khronos Group Inc.
+** Copyright 2013-2020 The Khronos Group Inc.
+** SPDX-License-Identifier: Apache-2.0
 **
-** Permission is hereby granted, free of charge, to any person obtaining a
-** copy of this software and/or associated documentation files (the
-** "Materials"), to deal in the Materials without restriction, including
-** without limitation the rights to use, copy, modify, merge, publish,
-** distribute, sublicense, and/or sell copies of the Materials, and to
-** permit persons to whom the Materials are furnished to do so, subject to
-** the following conditions:
-**
-** The above copyright notice and this permission notice shall be included
-** in all copies or substantial portions of the Materials.
-**
-** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-*/
-/*
-** This header is generated from the Khronos OpenGL / OpenGL ES XML
-** API Registry. The current version of the Registry, generator scripts
+** This header is generated from the Khronos EGL XML API Registry.
+** The current version of the Registry, generator scripts
 ** used to make the header, and the header can be found at
 **   http://www.khronos.org/registry/egl
 **
-** Khronos $Git commit SHA1: bae3518c48 $ on $Git commit date: 2018-05-17 10:56:57 -0700 $
+** Khronos $Git commit SHA1: 800219cd6e $ on $Git commit date: 2024-05-13 00:13:13 -0700 $
 */
 
 #include <EGL/eglplatform.h>
 
-/* Generated on date 20180517 */
+#ifndef EGL_EGL_PROTOTYPES
+#define EGL_EGL_PROTOTYPES 1
+#endif
+
+/* Generated on date 20240715 */
 
 /* Generated C header for:
  * API: egl
@@ -118,6 +103,31 @@
 #define EGL_VERSION                       0x3054
 #define EGL_WIDTH                         0x3057
 #define EGL_WINDOW_BIT                    0x0004
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLCHOOSECONFIGPROC) (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLCOPYBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target);
+typedef EGLContext (EGLAPIENTRYP PFNEGLCREATECONTEXTPROC) (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERSURFACEPROC) (EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGATTRIBPROC) (EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETCONFIGSPROC) (EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config);
+typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETCURRENTDISPLAYPROC) (void);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLGETCURRENTSURFACEPROC) (EGLint readdraw);
+typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETDISPLAYPROC) (EGLNativeDisplayType display_id);
+typedef EGLint (EGLAPIENTRYP PFNEGLGETERRORPROC) (void);
+typedef __eglMustCastToProperFunctionPointerType (EGLAPIENTRYP PFNEGLGETPROCADDRESSPROC) (const char *procname);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLINITIALIZEPROC) (EGLDisplay dpy, EGLint *major, EGLint *minor);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLMAKECURRENTPROC) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYCONTEXTPROC) (EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value);
+typedef const char *(EGLAPIENTRYP PFNEGLQUERYSTRINGPROC) (EGLDisplay dpy, EGLint name);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSURFACEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSPROC) (EGLDisplay dpy, EGLSurface surface);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLTERMINATEPROC) (EGLDisplay dpy);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITGLPROC) (void);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITNATIVEPROC) (EGLint engine);
+#if EGL_EGL_PROTOTYPES
 EGLAPI EGLBoolean EGLAPIENTRY eglChooseConfig (EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config);
 EGLAPI EGLBoolean EGLAPIENTRY eglCopyBuffers (EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target);
 EGLAPI EGLContext EGLAPIENTRY eglCreateContext (EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list);
@@ -142,6 +152,7 @@
 EGLAPI EGLBoolean EGLAPIENTRY eglTerminate (EGLDisplay dpy);
 EGLAPI EGLBoolean EGLAPIENTRY eglWaitGL (void);
 EGLAPI EGLBoolean EGLAPIENTRY eglWaitNative (EGLint engine);
+#endif
 #endif /* EGL_VERSION_1_0 */
 
 #ifndef EGL_VERSION_1_1
@@ -160,10 +171,16 @@
 #define EGL_TEXTURE_RGB                   0x305D
 #define EGL_TEXTURE_RGBA                  0x305E
 #define EGL_TEXTURE_TARGET                0x3081
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDTEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETEXIMAGEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint buffer);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSURFACEATTRIBPROC) (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPINTERVALPROC) (EGLDisplay dpy, EGLint interval);
+#if EGL_EGL_PROTOTYPES
 EGLAPI EGLBoolean EGLAPIENTRY eglBindTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer);
 EGLAPI EGLBoolean EGLAPIENTRY eglReleaseTexImage (EGLDisplay dpy, EGLSurface surface, EGLint buffer);
 EGLAPI EGLBoolean EGLAPIENTRY eglSurfaceAttrib (EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value);
 EGLAPI EGLBoolean EGLAPIENTRY eglSwapInterval (EGLDisplay dpy, EGLint interval);
+#endif
 #endif /* EGL_VERSION_1_1 */
 
 #ifndef EGL_VERSION_1_2
@@ -199,11 +216,18 @@
 #define EGL_SWAP_BEHAVIOR                 0x3093
 #define EGL_UNKNOWN                       EGL_CAST(EGLint,-1)
 #define EGL_VERTICAL_RESOLUTION           0x3091
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDAPIPROC) (EGLenum api);
+typedef EGLenum (EGLAPIENTRYP PFNEGLQUERYAPIPROC) (void);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPBUFFERFROMCLIENTBUFFERPROC) (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLRELEASETHREADPROC) (void);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITCLIENTPROC) (void);
+#if EGL_EGL_PROTOTYPES
 EGLAPI EGLBoolean EGLAPIENTRY eglBindAPI (EGLenum api);
 EGLAPI EGLenum EGLAPIENTRY eglQueryAPI (void);
 EGLAPI EGLSurface EGLAPIENTRY eglCreatePbufferFromClientBuffer (EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list);
 EGLAPI EGLBoolean EGLAPIENTRY eglReleaseThread (void);
 EGLAPI EGLBoolean EGLAPIENTRY eglWaitClient (void);
+#endif
 #endif /* EGL_VERSION_1_2 */
 
 #ifndef EGL_VERSION_1_3
@@ -232,7 +256,10 @@
 #define EGL_OPENGL_API                    0x30A2
 #define EGL_OPENGL_BIT                    0x0008
 #define EGL_SWAP_BEHAVIOR_PRESERVED_BIT   0x0400
+typedef EGLContext (EGLAPIENTRYP PFNEGLGETCURRENTCONTEXTPROC) (void);
+#if EGL_EGL_PROTOTYPES
 EGLAPI EGLContext EGLAPIENTRY eglGetCurrentContext (void);
+#endif
 #endif /* EGL_VERSION_1_4 */
 
 #ifndef EGL_VERSION_1_5
@@ -284,6 +311,17 @@
 #define EGL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x30B8
 #define EGL_IMAGE_PRESERVED               0x30D2
 #define EGL_NO_IMAGE                      EGL_CAST(EGLImage,0)
+typedef EGLSync (EGLAPIENTRYP PFNEGLCREATESYNCPROC) (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYSYNCPROC) (EGLDisplay dpy, EGLSync sync);
+typedef EGLint (EGLAPIENTRYP PFNEGLCLIENTWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETSYNCATTRIBPROC) (EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib *value);
+typedef EGLImage (EGLAPIENTRYP PFNEGLCREATEIMAGEPROC) (EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLAttrib *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLDESTROYIMAGEPROC) (EGLDisplay dpy, EGLImage image);
+typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYPROC) (EGLenum platform, void *native_display, const EGLAttrib *attrib_list);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list);
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMPIXMAPSURFACEPROC) (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLWAITSYNCPROC) (EGLDisplay dpy, EGLSync sync, EGLint flags);
+#if EGL_EGL_PROTOTYPES
 EGLAPI EGLSync EGLAPIENTRY eglCreateSync (EGLDisplay dpy, EGLenum type, const EGLAttrib *attrib_list);
 EGLAPI EGLBoolean EGLAPIENTRY eglDestroySync (EGLDisplay dpy, EGLSync sync);
 EGLAPI EGLint EGLAPIENTRY eglClientWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
@@ -294,6 +332,7 @@
 EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list);
 EGLAPI EGLSurface EGLAPIENTRY eglCreatePlatformPixmapSurface (EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLAttrib *attrib_list);
 EGLAPI EGLBoolean EGLAPIENTRY eglWaitSync (EGLDisplay dpy, EGLSync sync, EGLint flags);
+#endif
 #endif /* EGL_VERSION_1_5 */
 
 #ifdef __cplusplus
diff --git a/opengl/include/EGL/eglext.h b/opengl/include/EGL/eglext.h
index c787fc9..4d14c69 100644
--- a/opengl/include/EGL/eglext.h
+++ b/opengl/include/EGL/eglext.h
@@ -6,39 +6,20 @@
 #endif
 
 /*
-** Copyright (c) 2013-2017 The Khronos Group Inc.
+** Copyright 2013-2020 The Khronos Group Inc.
+** SPDX-License-Identifier: Apache-2.0
 **
-** Permission is hereby granted, free of charge, to any person obtaining a
-** copy of this software and/or associated documentation files (the
-** "Materials"), to deal in the Materials without restriction, including
-** without limitation the rights to use, copy, modify, merge, publish,
-** distribute, sublicense, and/or sell copies of the Materials, and to
-** permit persons to whom the Materials are furnished to do so, subject to
-** the following conditions:
-**
-** The above copyright notice and this permission notice shall be included
-** in all copies or substantial portions of the Materials.
-**
-** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
-*/
-/*
 ** This header is generated from the Khronos EGL XML API Registry.
 ** The current version of the Registry, generator scripts
 ** used to make the header, and the header can be found at
 **   http://www.khronos.org/registry/egl
 **
-** Khronos $Git commit SHA1: 726475c203 $ on $Git commit date: 2018-10-03 23:51:49 -0700 $
+** Khronos $Git commit SHA1: 800219cd6e $ on $Git commit date: 2024-05-13 00:13:13 -0700 $
 */
 
 #include <EGL/eglplatform.h>
 
-#define EGL_EGLEXT_VERSION 20181204
+#define EGL_EGLEXT_VERSION 20240715
 
 /* Generated C header for:
  * API: egl
@@ -443,9 +424,9 @@
 
 #ifndef EGL_KHR_swap_buffers_with_damage
 #define EGL_KHR_swap_buffers_with_damage 1
-typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects);
 #ifdef EGL_EGLEXT_PROTOTYPES
-EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects);
+EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects);
 #endif
 #endif /* EGL_KHR_swap_buffers_with_damage */
 
@@ -462,6 +443,10 @@
 #endif
 #endif /* EGL_KHR_wait_sync */
 
+#ifndef EGL_ANDROID_GLES_layers
+#define EGL_ANDROID_GLES_layers 1
+#endif /* EGL_ANDROID_GLES_layers */
+
 #ifndef EGL_ANDROID_blob_cache
 #define EGL_ANDROID_blob_cache 1
 typedef khronos_ssize_t EGLsizeiANDROID;
@@ -566,6 +551,11 @@
 #define EGL_RECORDABLE_ANDROID            0x3142
 #endif /* EGL_ANDROID_recordable */
 
+#ifndef EGL_ANDROID_telemetry_hint
+#define EGL_ANDROID_telemetry_hint 1
+#define EGL_TELEMETRY_HINT_ANDROID        0x3570
+#endif /* EGL_ANDROID_telemetry_hint */
+
 #ifndef EGL_ANGLE_d3d_share_handle_client_buffer
 #define EGL_ANGLE_d3d_share_handle_client_buffer 1
 #define EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE 0x3200
@@ -589,11 +579,25 @@
 #define EGL_ANGLE_surface_d3d_texture_2d_share_handle 1
 #endif /* EGL_ANGLE_surface_d3d_texture_2d_share_handle */
 
+#ifndef EGL_ANGLE_sync_control_rate
+#define EGL_ANGLE_sync_control_rate 1
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLGETMSCRATEANGLEPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *numerator, EGLint *denominator);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLBoolean EGLAPIENTRY eglGetMscRateANGLE (EGLDisplay dpy, EGLSurface surface, EGLint *numerator, EGLint *denominator);
+#endif
+#endif /* EGL_ANGLE_sync_control_rate */
+
 #ifndef EGL_ANGLE_window_fixed_size
 #define EGL_ANGLE_window_fixed_size 1
 #define EGL_FIXED_SIZE_ANGLE              0x3201
 #endif /* EGL_ANGLE_window_fixed_size */
 
+#ifndef EGL_ARM_image_format
+#define EGL_ARM_image_format 1
+#define EGL_COLOR_COMPONENT_TYPE_UNSIGNED_INTEGER_ARM 0x3287
+#define EGL_COLOR_COMPONENT_TYPE_INTEGER_ARM 0x3288
+#endif /* EGL_ARM_image_format */
+
 #ifndef EGL_ARM_implicit_external_sync
 #define EGL_ARM_implicit_external_sync 1
 #define EGL_SYNC_PRIOR_COMMANDS_IMPLICIT_EXTERNAL_ARM 0x328A
@@ -652,6 +656,11 @@
 #endif
 #endif /* EGL_EXT_compositor */
 
+#ifndef EGL_EXT_config_select_group
+#define EGL_EXT_config_select_group 1
+#define EGL_CONFIG_SELECT_GROUP_EXT       0x34C0
+#endif /* EGL_EXT_config_select_group */
+
 #ifndef EGL_EXT_create_context_robustness
 #define EGL_EXT_create_context_robustness 1
 #define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT 0x30BF
@@ -684,6 +693,11 @@
 #define EGL_DRM_MASTER_FD_EXT             0x333C
 #endif /* EGL_EXT_device_drm */
 
+#ifndef EGL_EXT_device_drm_render_node
+#define EGL_EXT_device_drm_render_node 1
+#define EGL_DRM_RENDER_NODE_FILE_EXT      0x3377
+#endif /* EGL_EXT_device_drm_render_node */
+
 #ifndef EGL_EXT_device_enumeration
 #define EGL_EXT_device_enumeration 1
 #endif /* EGL_EXT_device_enumeration */
@@ -691,12 +705,33 @@
 #ifndef EGL_EXT_device_openwf
 #define EGL_EXT_device_openwf 1
 #define EGL_OPENWF_DEVICE_ID_EXT          0x3237
+#define EGL_OPENWF_DEVICE_EXT             0x333D
 #endif /* EGL_EXT_device_openwf */
 
+#ifndef EGL_EXT_device_persistent_id
+#define EGL_EXT_device_persistent_id 1
+#define EGL_DEVICE_UUID_EXT               0x335C
+#define EGL_DRIVER_UUID_EXT               0x335D
+#define EGL_DRIVER_NAME_EXT               0x335E
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYDEVICEBINARYEXTPROC) (EGLDeviceEXT device, EGLint name, EGLint max_size, void *value, EGLint *size);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLBoolean EGLAPIENTRY eglQueryDeviceBinaryEXT (EGLDeviceEXT device, EGLint name, EGLint max_size, void *value, EGLint *size);
+#endif
+#endif /* EGL_EXT_device_persistent_id */
+
 #ifndef EGL_EXT_device_query
 #define EGL_EXT_device_query 1
 #endif /* EGL_EXT_device_query */
 
+#ifndef EGL_EXT_device_query_name
+#define EGL_EXT_device_query_name 1
+#define EGL_RENDERER_EXT                  0x335F
+#endif /* EGL_EXT_device_query_name */
+
+#ifndef EGL_EXT_explicit_device
+#define EGL_EXT_explicit_device 1
+#endif /* EGL_EXT_explicit_device */
+
 #ifndef EGL_EXT_gl_colorspace_bt2020_hlg
 #define EGL_EXT_gl_colorspace_bt2020_hlg 1
 #define EGL_GL_COLORSPACE_BT2020_HLG_EXT  0x3540
@@ -878,6 +913,17 @@
 #define EGL_PLATFORM_X11_SCREEN_EXT       0x31D6
 #endif /* EGL_EXT_platform_x11 */
 
+#ifndef EGL_EXT_platform_xcb
+#define EGL_EXT_platform_xcb 1
+#define EGL_PLATFORM_XCB_EXT              0x31DC
+#define EGL_PLATFORM_XCB_SCREEN_EXT       0x31DE
+#endif /* EGL_EXT_platform_xcb */
+
+#ifndef EGL_EXT_present_opaque
+#define EGL_EXT_present_opaque 1
+#define EGL_PRESENT_OPAQUE_EXT            0x31DF
+#endif /* EGL_EXT_present_opaque */
+
 #ifndef EGL_EXT_protected_content
 #define EGL_EXT_protected_content 1
 #define EGL_PROTECTED_CONTENT_EXT         0x32C0
@@ -887,6 +933,10 @@
 #define EGL_EXT_protected_surface 1
 #endif /* EGL_EXT_protected_surface */
 
+#ifndef EGL_EXT_query_reset_notification_strategy
+#define EGL_EXT_query_reset_notification_strategy 1
+#endif /* EGL_EXT_query_reset_notification_strategy */
+
 #ifndef EGL_EXT_stream_consumer_egloutput
 #define EGL_EXT_stream_consumer_egloutput 1
 typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMCONSUMEROUTPUTEXTPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLOutputLayerEXT layer);
@@ -916,11 +966,36 @@
 #define EGL_METADATA_SCALING_EXT          50000
 #endif /* EGL_EXT_surface_SMPTE2086_metadata */
 
+#ifndef EGL_EXT_surface_compression
+#define EGL_EXT_surface_compression 1
+#define EGL_SURFACE_COMPRESSION_EXT       0x34B0
+#define EGL_SURFACE_COMPRESSION_PLANE1_EXT 0x328E
+#define EGL_SURFACE_COMPRESSION_PLANE2_EXT 0x328F
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_NONE_EXT 0x34B1
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_DEFAULT_EXT 0x34B2
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_1BPC_EXT 0x34B4
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_2BPC_EXT 0x34B5
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_3BPC_EXT 0x34B6
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_4BPC_EXT 0x34B7
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_5BPC_EXT 0x34B8
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_6BPC_EXT 0x34B9
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_7BPC_EXT 0x34BA
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_8BPC_EXT 0x34BB
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_9BPC_EXT 0x34BC
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_10BPC_EXT 0x34BD
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_11BPC_EXT 0x34BE
+#define EGL_SURFACE_COMPRESSION_FIXED_RATE_12BPC_EXT 0x34BF
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYSUPPORTEDCOMPRESSIONRATESEXTPROC) (EGLDisplay dpy, EGLConfig config, const EGLAttrib *attrib_list, EGLint *rates, EGLint rate_size, EGLint *num_rates);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLBoolean EGLAPIENTRY eglQuerySupportedCompressionRatesEXT (EGLDisplay dpy, EGLConfig config, const EGLAttrib *attrib_list, EGLint *rates, EGLint rate_size, EGLint *num_rates);
+#endif
+#endif /* EGL_EXT_surface_compression */
+
 #ifndef EGL_EXT_swap_buffers_with_damage
 #define EGL_EXT_swap_buffers_with_damage 1
-typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSWITHDAMAGEEXTPROC) (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects);
 #ifdef EGL_EGLEXT_PROTOTYPES
-EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT (EGLDisplay dpy, EGLSurface surface, EGLint *rects, EGLint n_rects);
+EGLAPI EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT (EGLDisplay dpy, EGLSurface surface, const EGLint *rects, EGLint n_rects);
 #endif
 #endif /* EGL_EXT_swap_buffers_with_damage */
 
@@ -1036,6 +1111,16 @@
 #define EGL_PLATFORM_SURFACELESS_MESA     0x31DD
 #endif /* EGL_MESA_platform_surfaceless */
 
+#ifndef EGL_MESA_query_driver
+#define EGL_MESA_query_driver 1
+typedef char *(EGLAPIENTRYP PFNEGLGETDISPLAYDRIVERCONFIGPROC) (EGLDisplay dpy);
+typedef const char *(EGLAPIENTRYP PFNEGLGETDISPLAYDRIVERNAMEPROC) (EGLDisplay dpy);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI char *EGLAPIENTRY eglGetDisplayDriverConfig (EGLDisplay dpy);
+EGLAPI const char *EGLAPIENTRY eglGetDisplayDriverName (EGLDisplay dpy);
+#endif
+#endif /* EGL_MESA_query_driver */
+
 #ifndef EGL_NOK_swap_region
 #define EGL_NOK_swap_region 1
 typedef EGLBoolean (EGLAPIENTRYP PFNEGLSWAPBUFFERSREGIONNOKPROC) (EGLDisplay dpy, EGLSurface surface, EGLint numRects, const EGLint *rects);
@@ -1124,11 +1209,39 @@
 #endif
 #endif /* EGL_NV_post_sub_buffer */
 
+#ifndef EGL_NV_quadruple_buffer
+#define EGL_NV_quadruple_buffer 1
+#define EGL_QUADRUPLE_BUFFER_NV           0x3231
+#endif /* EGL_NV_quadruple_buffer */
+
 #ifndef EGL_NV_robustness_video_memory_purge
 #define EGL_NV_robustness_video_memory_purge 1
 #define EGL_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x334C
 #endif /* EGL_NV_robustness_video_memory_purge */
 
+#ifndef EGL_NV_stream_consumer_eglimage
+#define EGL_NV_stream_consumer_eglimage 1
+#define EGL_STREAM_CONSUMER_IMAGE_NV      0x3373
+#define EGL_STREAM_IMAGE_ADD_NV           0x3374
+#define EGL_STREAM_IMAGE_REMOVE_NV        0x3375
+#define EGL_STREAM_IMAGE_AVAILABLE_NV     0x3376
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMIMAGECONSUMERCONNECTNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLint num_modifiers, const EGLuint64KHR *modifiers, const EGLAttrib *attrib_list);
+typedef EGLint (EGLAPIENTRYP PFNEGLQUERYSTREAMCONSUMEREVENTNVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLTime timeout, EGLenum *event, EGLAttrib *aux);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMACQUIREIMAGENVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLImage *pImage, EGLSync sync);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLSTREAMRELEASEIMAGENVPROC) (EGLDisplay dpy, EGLStreamKHR stream, EGLImage image, EGLSync sync);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLBoolean EGLAPIENTRY eglStreamImageConsumerConnectNV (EGLDisplay dpy, EGLStreamKHR stream, EGLint num_modifiers, const EGLuint64KHR *modifiers, const EGLAttrib *attrib_list);
+EGLAPI EGLint EGLAPIENTRY eglQueryStreamConsumerEventNV (EGLDisplay dpy, EGLStreamKHR stream, EGLTime timeout, EGLenum *event, EGLAttrib *aux);
+EGLAPI EGLBoolean EGLAPIENTRY eglStreamAcquireImageNV (EGLDisplay dpy, EGLStreamKHR stream, EGLImage *pImage, EGLSync sync);
+EGLAPI EGLBoolean EGLAPIENTRY eglStreamReleaseImageNV (EGLDisplay dpy, EGLStreamKHR stream, EGLImage image, EGLSync sync);
+#endif
+#endif /* EGL_NV_stream_consumer_eglimage */
+
+#ifndef EGL_NV_stream_consumer_eglimage_use_scanout_attrib
+#define EGL_NV_stream_consumer_eglimage_use_scanout_attrib 1
+#define EGL_STREAM_CONSUMER_IMAGE_USE_SCANOUT_NV 0x3378
+#endif /* EGL_NV_stream_consumer_eglimage_use_scanout_attrib */
+
 #ifndef EGL_NV_stream_consumer_gltexture_yuv
 #define EGL_NV_stream_consumer_gltexture_yuv 1
 #define EGL_YUV_PLANE0_TEXTURE_UNIT_NV    0x332C
@@ -1165,6 +1278,12 @@
 #define EGL_STREAM_CROSS_SYSTEM_NV        0x334F
 #endif /* EGL_NV_stream_cross_system */
 
+#ifndef EGL_NV_stream_dma
+#define EGL_NV_stream_dma 1
+#define EGL_STREAM_DMA_NV                 0x3371
+#define EGL_STREAM_DMA_SERVER_NV          0x3372
+#endif /* EGL_NV_stream_dma */
+
 #ifndef EGL_NV_stream_fifo_next
 #define EGL_NV_stream_fifo_next 1
 #define EGL_PENDING_FRAME_NV              0x3329
@@ -1216,6 +1335,21 @@
 #endif
 #endif /* EGL_NV_stream_metadata */
 
+#ifndef EGL_NV_stream_origin
+#define EGL_NV_stream_origin 1
+#define EGL_STREAM_FRAME_ORIGIN_X_NV      0x3366
+#define EGL_STREAM_FRAME_ORIGIN_Y_NV      0x3367
+#define EGL_STREAM_FRAME_MAJOR_AXIS_NV    0x3368
+#define EGL_CONSUMER_AUTO_ORIENTATION_NV  0x3369
+#define EGL_PRODUCER_AUTO_ORIENTATION_NV  0x336A
+#define EGL_LEFT_NV                       0x336B
+#define EGL_RIGHT_NV                      0x336C
+#define EGL_TOP_NV                        0x336D
+#define EGL_BOTTOM_NV                     0x336E
+#define EGL_X_AXIS_NV                     0x336F
+#define EGL_Y_AXIS_NV                     0x3370
+#endif /* EGL_NV_stream_origin */
+
 #ifndef EGL_NV_stream_remote
 #define EGL_NV_stream_remote 1
 #define EGL_STREAM_STATE_INITIALIZING_NV  0x3240
@@ -1312,6 +1446,21 @@
 #endif /* KHRONOS_SUPPORT_INT64 */
 #endif /* EGL_NV_system_time */
 
+#ifndef EGL_NV_triple_buffer
+#define EGL_NV_triple_buffer 1
+#define EGL_TRIPLE_BUFFER_NV              0x3230
+#endif /* EGL_NV_triple_buffer */
+
+#ifndef EGL_QNX_image_native_buffer
+#define EGL_QNX_image_native_buffer 1
+#define EGL_NATIVE_BUFFER_QNX             0x3551
+#endif /* EGL_QNX_image_native_buffer */
+
+#ifndef EGL_QNX_platform_screen
+#define EGL_QNX_platform_screen 1
+#define EGL_PLATFORM_SCREEN_QNX           0x3550
+#endif /* EGL_QNX_platform_screen */
+
 #ifndef EGL_TIZEN_image_native_buffer
 #define EGL_TIZEN_image_native_buffer 1
 #define EGL_NATIVE_BUFFER_TIZEN           0x32A0
@@ -1322,6 +1471,40 @@
 #define EGL_NATIVE_SURFACE_TIZEN          0x32A1
 #endif /* EGL_TIZEN_image_native_surface */
 
+#ifndef EGL_WL_bind_wayland_display
+#define EGL_WL_bind_wayland_display 1
+#define PFNEGLBINDWAYLANDDISPLAYWL PFNEGLBINDWAYLANDDISPLAYWLPROC
+#define PFNEGLUNBINDWAYLANDDISPLAYWL PFNEGLUNBINDWAYLANDDISPLAYWLPROC
+#define PFNEGLQUERYWAYLANDBUFFERWL PFNEGLQUERYWAYLANDBUFFERWLPROC
+struct wl_display;
+struct wl_resource;
+#define EGL_WAYLAND_BUFFER_WL             0x31D5
+#define EGL_WAYLAND_PLANE_WL              0x31D6
+#define EGL_TEXTURE_Y_U_V_WL              0x31D7
+#define EGL_TEXTURE_Y_UV_WL               0x31D8
+#define EGL_TEXTURE_Y_XUXV_WL             0x31D9
+#define EGL_TEXTURE_EXTERNAL_WL           0x31DA
+#define EGL_WAYLAND_Y_INVERTED_WL         0x31DB
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLBINDWAYLANDDISPLAYWLPROC) (EGLDisplay dpy, struct wl_display *display);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLUNBINDWAYLANDDISPLAYWLPROC) (EGLDisplay dpy, struct wl_display *display);
+typedef EGLBoolean (EGLAPIENTRYP PFNEGLQUERYWAYLANDBUFFERWLPROC) (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI EGLBoolean EGLAPIENTRY eglBindWaylandDisplayWL (EGLDisplay dpy, struct wl_display *display);
+EGLAPI EGLBoolean EGLAPIENTRY eglUnbindWaylandDisplayWL (EGLDisplay dpy, struct wl_display *display);
+EGLAPI EGLBoolean EGLAPIENTRY eglQueryWaylandBufferWL (EGLDisplay dpy, struct wl_resource *buffer, EGLint attribute, EGLint *value);
+#endif
+#endif /* EGL_WL_bind_wayland_display */
+
+#ifndef EGL_WL_create_wayland_buffer_from_image
+#define EGL_WL_create_wayland_buffer_from_image 1
+#define PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWL PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC
+struct wl_buffer;
+typedef struct wl_buffer *(EGLAPIENTRYP PFNEGLCREATEWAYLANDBUFFERFROMIMAGEWLPROC) (EGLDisplay dpy, EGLImageKHR image);
+#ifdef EGL_EGLEXT_PROTOTYPES
+EGLAPI struct wl_buffer *EGLAPIENTRY eglCreateWaylandBufferFromImageWL (EGLDisplay dpy, EGLImageKHR image);
+#endif
+#endif /* EGL_WL_create_wayland_buffer_from_image */
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/opengl/include/EGL/eglplatform.h b/opengl/include/EGL/eglplatform.h
index 0bc2cb9..6786afd 100644
--- a/opengl/include/EGL/eglplatform.h
+++ b/opengl/include/EGL/eglplatform.h
@@ -2,36 +2,17 @@
 #define __eglplatform_h_
 
 /*
-** Copyright (c) 2007-2016 The Khronos Group Inc.
-**
-** Permission is hereby granted, free of charge, to any person obtaining a
-** copy of this software and/or associated documentation files (the
-** "Materials"), to deal in the Materials without restriction, including
-** without limitation the rights to use, copy, modify, merge, publish,
-** distribute, sublicense, and/or sell copies of the Materials, and to
-** permit persons to whom the Materials are furnished to do so, subject to
-** the following conditions:
-**
-** The above copyright notice and this permission notice shall be included
-** in all copies or substantial portions of the Materials.
-**
-** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
+** Copyright 2007-2020 The Khronos Group Inc.
+** SPDX-License-Identifier: Apache-2.0
 */
 
 /* Platform-specific types and definitions for egl.h
- * $Revision: 30994 $ on $Date: 2015-04-30 13:36:48 -0700 (Thu, 30 Apr 2015) $
  *
  * Adopters may modify khrplatform.h and this file to suit their platform.
  * You are encouraged to submit all modifications to the Khronos group so that
  * they can be included in future versions of this file.  Please submit changes
- * by sending them to the public Khronos Bugzilla (http://khronos.org/bugzilla)
- * by filing a bug against product "EGL" component "Registry".
+ * by filing an issue or pull request on the public Khronos EGL Registry, at
+ * https://www.github.com/KhronosGroup/EGL-Registry/
  */
 
 #include <KHR/khrplatform.h>
@@ -67,7 +48,13 @@
  * implementations.
  */
 
-#if defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */
+#if defined(EGL_NO_PLATFORM_SPECIFIC_TYPES)
+
+typedef void *EGLNativeDisplayType;
+typedef void *EGLNativePixmapType;
+typedef void *EGLNativeWindowType;
+
+#elif defined(_WIN32) || defined(__VC32__) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) /* Win32 and WinCE */
 #ifndef WIN32_LEAN_AND_MEAN
 #define WIN32_LEAN_AND_MEAN 1
 #endif
@@ -77,11 +64,23 @@
 typedef HBITMAP EGLNativePixmapType;
 typedef HWND    EGLNativeWindowType;
 
+#elif defined(__QNX__)
+
+typedef khronos_uintptr_t      EGLNativeDisplayType;
+typedef struct _screen_pixmap* EGLNativePixmapType;  /* screen_pixmap_t */
+typedef struct _screen_window* EGLNativeWindowType;  /* screen_window_t */
+
+#elif defined(__EMSCRIPTEN__)
+
+typedef int EGLNativeDisplayType;
+typedef int EGLNativePixmapType;
+typedef int EGLNativeWindowType;
+
 #elif defined(__WINSCW__) || defined(__SYMBIAN32__)  /* Symbian */
 
 typedef int   EGLNativeDisplayType;
-typedef void *EGLNativeWindowType;
 typedef void *EGLNativePixmapType;
+typedef void *EGLNativeWindowType;
 
 #elif defined(WL_EGL_PLATFORM)
 
@@ -100,17 +99,17 @@
 struct ANativeWindow;
 struct egl_native_pixmap_t;
 
-typedef struct ANativeWindow*           EGLNativeWindowType;
-typedef struct egl_native_pixmap_t*     EGLNativePixmapType;
 typedef void*                           EGLNativeDisplayType;
+typedef struct egl_native_pixmap_t*     EGLNativePixmapType;
+typedef struct ANativeWindow*           EGLNativeWindowType;
 
 #elif defined(USE_OZONE)
 
 typedef intptr_t EGLNativeDisplayType;
-typedef intptr_t EGLNativeWindowType;
 typedef intptr_t EGLNativePixmapType;
+typedef intptr_t EGLNativeWindowType;
 
-#elif defined(__unix__) || defined(USE_X11)
+#elif defined(USE_X11)
 
 /* X11 (tentative)  */
 #include <X11/Xlib.h>
@@ -120,11 +119,17 @@
 typedef Pixmap   EGLNativePixmapType;
 typedef Window   EGLNativeWindowType;
 
+#elif defined(__unix__)
+
+typedef void             *EGLNativeDisplayType;
+typedef khronos_uintptr_t EGLNativePixmapType;
+typedef khronos_uintptr_t EGLNativeWindowType;
+
 #elif defined(__APPLE__)
 
 typedef int   EGLNativeDisplayType;
-typedef void *EGLNativeWindowType;
 typedef void *EGLNativePixmapType;
+typedef void *EGLNativeWindowType;
 
 #elif defined(__HAIKU__)
 
@@ -134,6 +139,12 @@
 typedef khronos_uintptr_t  EGLNativePixmapType;
 typedef khronos_uintptr_t  EGLNativeWindowType;
 
+#elif defined(__Fuchsia__)
+
+typedef void              *EGLNativeDisplayType;
+typedef khronos_uintptr_t  EGLNativePixmapType;
+typedef khronos_uintptr_t  EGLNativeWindowType;
+
 #else
 #error "Platform not recognized"
 #endif
diff --git a/opengl/libs/Android.bp b/opengl/libs/Android.bp
index 16de390..b19a862 100644
--- a/opengl/libs/Android.bp
+++ b/opengl/libs/Android.bp
@@ -40,9 +40,6 @@
     symbol_file: "libEGL.map.txt",
     first_version: "9",
     unversioned_until: "current",
-    export_header_libs: [
-        "libEGL_headers",
-    ],
 }
 
 ndk_library {
@@ -50,9 +47,6 @@
     symbol_file: "libGLESv1_CM.map.txt",
     first_version: "9",
     unversioned_until: "current",
-    export_header_libs: [
-        "libGLESv1_CM_headers",
-    ],
 }
 
 ndk_library {
@@ -60,9 +54,6 @@
     symbol_file: "libGLESv2.map.txt",
     first_version: "9",
     unversioned_until: "current",
-    export_header_libs: [
-        "libGLESv2_headers",
-    ],
 }
 
 ndk_library {
@@ -70,9 +61,6 @@
     symbol_file: "libGLESv3.map.txt",
     first_version: "18",
     unversioned_until: "current",
-    export_header_libs: [
-        "libGLESv3_headers",
-    ],
 }
 
 cc_defaults {
@@ -147,6 +135,9 @@
         "EGL/MultifileBlobCache.cpp",
     ],
     export_include_dirs: ["EGL"],
+    shared_libs: [
+        "libz",
+    ],
 }
 
 cc_library_shared {
@@ -181,6 +172,7 @@
         "libutils",
         "libSurfaceFlingerProp",
         "libunwindstack",
+        "libz",
     ],
     static_libs: [
         "libEGL_getProcAddress",
@@ -211,6 +203,7 @@
     ],
     shared_libs: [
         "libutils",
+        "libz",
     ],
 }
 
diff --git a/opengl/libs/EGL/FileBlobCache.cpp b/opengl/libs/EGL/FileBlobCache.cpp
index 4a0fac4..573ca54 100644
--- a/opengl/libs/EGL/FileBlobCache.cpp
+++ b/opengl/libs/EGL/FileBlobCache.cpp
@@ -27,6 +27,7 @@
 
 #include <log/log.h>
 #include <utils/Trace.h>
+#include <zlib.h>
 
 // Cache file header
 static const char* cacheFileMagic = "EGL$";
@@ -34,20 +35,10 @@
 
 namespace android {
 
-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++) {
-        r ^= buf[i];
-        for (int j = 0; j < 8; j++) {
-            if (r & 1) {
-                r = (r >> 1) ^ polyBits;
-            } else {
-                r >>= 1;
-            }
-        }
-    }
-    return r;
+uint32_t GenerateCRC32(const uint8_t *data, size_t size)
+{
+    const unsigned long initialValue = crc32_z(0u, nullptr, 0u);
+    return static_cast<uint32_t>(crc32_z(initialValue, data, size));
 }
 
 FileBlobCache::FileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
@@ -101,7 +92,7 @@
             return;
         }
         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
-        if (crc32c(buf + headerSize, cacheSize) != *crc) {
+        if (GenerateCRC32(buf + headerSize, cacheSize) != *crc) {
             ALOGE("cache file failed CRC check");
             close(fd);
             return;
@@ -175,7 +166,7 @@
         // Write the file magic and CRC
         memcpy(buf, cacheFileMagic, 4);
         uint32_t* crc = reinterpret_cast<uint32_t*>(buf + 4);
-        *crc = crc32c(buf + headerSize, cacheSize);
+        *crc = GenerateCRC32(buf + headerSize, cacheSize);
 
         if (write(fd, buf, fileSize) == -1) {
             ALOGE("error writing cache file: %s (%d)", strerror(errno),
diff --git a/opengl/libs/EGL/FileBlobCache.h b/opengl/libs/EGL/FileBlobCache.h
index f083b0d..224444d 100644
--- a/opengl/libs/EGL/FileBlobCache.h
+++ b/opengl/libs/EGL/FileBlobCache.h
@@ -22,7 +22,7 @@
 
 namespace android {
 
-uint32_t crc32c(const uint8_t* buf, size_t len);
+uint32_t GenerateCRC32(const uint8_t *data, size_t size);
 
 class FileBlobCache : public BlobCache {
 public:
diff --git a/opengl/libs/EGL/Loader.cpp b/opengl/libs/EGL/Loader.cpp
index af0bcff..fed6afc 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -184,6 +184,14 @@
         }
     }
 
+    // Return true if app requests to use ANGLE, but ANGLE is not loaded.
+    // Difference with the case above is on devices that don't have an ANGLE apk installed,
+    // ANGLE namespace is not set. In that case if ANGLE in system partition is not loaded,
+    // we should unload the system driver first, and then load ANGLE from system partition.
+    if (!cnx->angleLoaded && android::GraphicsEnv::getInstance().shouldUseAngle()) {
+        return true;
+    }
+
     // Return true if native GLES drivers should be used and ANGLE is already loaded.
     if (android::GraphicsEnv::getInstance().shouldUseNativeDriver() && cnx->angleLoaded) {
         return true;
@@ -262,7 +270,7 @@
         hnd = attempt_to_load_updated_driver(cnx);
 
         // If updated driver apk is set but fail to load, abort here.
-        LOG_ALWAYS_FATAL_IF(android::GraphicsEnv::getInstance().getDriverNamespace(),
+        LOG_ALWAYS_FATAL_IF(android::GraphicsEnv::getInstance().getDriverNamespace() && !hnd,
                             "couldn't find an OpenGL ES implementation from %s",
                             android::GraphicsEnv::getInstance().getDriverPath().c_str());
     }
@@ -540,6 +548,10 @@
                 .flags = ANDROID_DLEXT_USE_NAMESPACE,
                 .library_namespace = ns,
         };
+        auto prop = base::GetProperty("debug.angle.libs.suffix", "");
+        if (!prop.empty()) {
+            name = std::string("lib") + kind + "_" + prop + ".so";
+        }
         so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
     }
 
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index 9905210..f7e33b3 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -214,9 +214,8 @@
                 }
 
                 // Ensure we have a good CRC
-                if (header.crc !=
-                    crc32c(mappedEntry + sizeof(MultifileHeader),
-                           fileSize - sizeof(MultifileHeader))) {
+                if (header.crc != GenerateCRC32(mappedEntry + sizeof(MultifileHeader),
+                                                fileSize - sizeof(MultifileHeader))) {
                     ALOGV("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));
@@ -532,9 +531,9 @@
             mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
 
     // Finally update the crc, using cacheVersion and everything the follows
-    status.crc =
-            crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
-                   sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+    status.crc = GenerateCRC32(
+        reinterpret_cast<uint8_t *>(&status) + offsetof(MultifileStatus, cacheVersion),
+        sizeof(status) - offsetof(MultifileStatus, cacheVersion));
 
     // Create the status file
     std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
@@ -599,9 +598,9 @@
     }
 
     // Ensure we have a good CRC
-    if (status.crc !=
-        crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
-               sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+    if (status.crc != GenerateCRC32(reinterpret_cast<uint8_t *>(&status) +
+                                        offsetof(MultifileStatus, cacheVersion),
+                                    sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
         ALOGE("STATUS(CHECK): Cache status failed CRC check!");
         return false;
     }
@@ -840,8 +839,8 @@
 
             // 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));
+            header->crc             = GenerateCRC32(buffer + sizeof(MultifileHeader),
+                                                    bufferSize - sizeof(MultifileHeader));
 
             ssize_t result = write(fd, buffer, bufferSize);
             if (result != bufferSize) {
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 18566c2..65aa2db 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -34,7 +34,7 @@
 
 namespace android {
 
-constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr uint32_t kMultifileBlobCacheVersion = 2;
 constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
 
 struct MultifileHeader {
diff --git a/opengl/libs/EGL/eglApi.cpp b/opengl/libs/EGL/eglApi.cpp
index 502c14f..8cb637b 100644
--- a/opengl/libs/EGL/eglApi.cpp
+++ b/opengl/libs/EGL/eglApi.cpp
@@ -248,7 +248,7 @@
     return cnx->platform.eglGetProcAddress(procname);
 }
 
-EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, EGLint* rects,
+EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, const EGLint* rects,
                                        EGLint n_rects) {
     ATRACE_CALL();
     clearError();
diff --git a/opengl/libs/EGL/egl_entries.in b/opengl/libs/EGL/egl_entries.in
index 1c91f1d..b6f2c34 100644
--- a/opengl/libs/EGL/egl_entries.in
+++ b/opengl/libs/EGL/egl_entries.in
@@ -106,5 +106,5 @@
 
 /* Partial update extensions */
 
-EGL_ENTRY(EGLBoolean, eglSwapBuffersWithDamageKHR, EGLDisplay, EGLSurface, EGLint *, EGLint)
+EGL_ENTRY(EGLBoolean, eglSwapBuffersWithDamageKHR, EGLDisplay, EGLSurface, const EGLint *, EGLint)
 EGL_ENTRY(EGLBoolean, eglSetDamageRegionKHR, EGLDisplay, EGLSurface, EGLint *, EGLint)
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index a6af713..6713a5c 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -916,42 +916,72 @@
             egl_context_t* const c = get_context(share_list);
             share_list = c->context;
         }
+
+        bool skip_telemetry = false;
+
+        auto findAttribute = [](const EGLint* attrib_ptr, GLint attribute, GLint* value) {
+            while (attrib_ptr && *attrib_ptr != EGL_NONE) {
+                GLint attr = *attrib_ptr++;
+                GLint val = *attrib_ptr++;
+                if (attr == attribute) {
+                    if (value) {
+                        *value = val;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        };
+
+        std::vector<EGLint> replacement_attrib_list;
+        GLint telemetry_value;
+        if (findAttribute(attrib_list, EGL_TELEMETRY_HINT_ANDROID, &telemetry_value)) {
+            skip_telemetry = (telemetry_value == android::GpuStatsInfo::SKIP_TELEMETRY);
+
+            // We need to remove EGL_TELEMETRY_HINT_ANDROID or the underlying drivers will
+            // complain about an unexpected attribute
+            const EGLint* attrib_ptr = attrib_list;
+            while (attrib_ptr && *attrib_ptr != EGL_NONE) {
+                GLint attr = *attrib_ptr++;
+                GLint val = *attrib_ptr++;
+                if (attr != EGL_TELEMETRY_HINT_ANDROID) {
+                    replacement_attrib_list.push_back(attr);
+                    replacement_attrib_list.push_back(val);
+                }
+            }
+            replacement_attrib_list.push_back(EGL_NONE);
+            attrib_list = replacement_attrib_list.data();
+        }
         // b/111083885 - If we are presenting EGL 1.4 interface to apps
         // error out on robust access attributes that are invalid
         // in EGL 1.4 as the driver may be fine with them but dEQP expects
         // tests to fail according to spec.
         if (attrib_list && (cnx->driverVersion < EGL_MAKE_VERSION(1, 5, 0))) {
-            const EGLint* attrib_ptr = attrib_list;
-            while (*attrib_ptr != EGL_NONE) {
-                GLint attr = *attrib_ptr++;
-                GLint value = *attrib_ptr++;
-                if (attr == EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR) {
-                    // We are GL ES context with EGL 1.4, this is an invalid
-                    // attribute
-                    return setError(EGL_BAD_ATTRIBUTE, EGL_NO_CONTEXT);
-                }
-            };
+            if (findAttribute(attrib_list, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR,
+                              nullptr)) {
+                // We are GL ES context with EGL 1.4, this is an invalid attribute
+                return setError(EGL_BAD_ATTRIBUTE, EGL_NO_CONTEXT);
+            }
         }
         EGLContext context =
                 cnx->egl.eglCreateContext(dp->disp.dpy, config, share_list, attrib_list);
         if (context != EGL_NO_CONTEXT) {
             // figure out if it's a GLESv1 or GLESv2
             int version = egl_connection_t::GLESv1_INDEX;
-            if (attrib_list) {
-                while (*attrib_list != EGL_NONE) {
-                    GLint attr = *attrib_list++;
-                    GLint value = *attrib_list++;
-                    if (attr == EGL_CONTEXT_CLIENT_VERSION && (value == 2 || value == 3)) {
-                        version = egl_connection_t::GLESv2_INDEX;
-                    }
-                };
+            GLint version_value;
+            if (findAttribute(attrib_list, EGL_CONTEXT_CLIENT_VERSION, &version_value)) {
+                if (version_value == 2 || version_value == 3) {
+                    version = egl_connection_t::GLESv2_INDEX;
+                }
             }
             if (version == egl_connection_t::GLESv1_INDEX) {
                 android::GraphicsEnv::getInstance().setTargetStats(
                         android::GpuStatsInfo::Stats::GLES_1_IN_USE);
             }
-            android::GraphicsEnv::getInstance().setTargetStats(
-                    android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT);
+            if (!skip_telemetry) {
+                android::GraphicsEnv::getInstance().setTargetStats(
+                        android::GpuStatsInfo::Stats::CREATED_GLES_CONTEXT);
+            }
             egl_context_t* c = new egl_context_t(dpy, context, config, cnx, version);
             return c;
         }
@@ -1324,7 +1354,7 @@
     std::mutex mMutex;
 };
 
-EGLBoolean eglSwapBuffersWithDamageKHRImpl(EGLDisplay dpy, EGLSurface draw, EGLint* rects,
+EGLBoolean eglSwapBuffersWithDamageKHRImpl(EGLDisplay dpy, EGLSurface draw, const EGLint* rects,
                                            EGLint n_rects) {
     const egl_display_t* dp = validate_display(dpy);
     if (!dp) return EGL_FALSE;
@@ -2111,6 +2141,10 @@
     }
 
     egl_surface_t const* const s = get_surface(surface);
+    if (!s->getNativeWindow()) {
+        setError(EGL_BAD_SURFACE, EGL_FALSE);
+        return EGL_FALSE;
+    }
     native_window_set_buffers_timestamp(s->getNativeWindow(), time);
 
     return EGL_TRUE;
@@ -2375,7 +2409,7 @@
         case 0:
             return EGL_TRUE;
         case -ENOENT:
-            return setError(EGL_BAD_ACCESS, (EGLBoolean)EGL_FALSE);
+            return setErrorQuiet(EGL_BAD_ACCESS, (EGLBoolean)EGL_FALSE);
         case -ENOSYS:
             return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
         case -EINVAL:
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
index 022a2a3..4947e5f 100644
--- a/opengl/libs/EGL/fuzzer/Android.bp
+++ b/opengl/libs/EGL/fuzzer/Android.bp
@@ -36,6 +36,10 @@
         "libutils",
     ],
 
+    shared_libs: [
+        "libz",
+    ],
+
     srcs: [
         "MultifileBlobCache_fuzzer.cpp",
     ],
diff --git a/opengl/libs/GLES2/gl2ext_api.in b/opengl/libs/GLES2/gl2ext_api.in
index 4a0d4b9..dc99e09 100644
--- a/opengl/libs/GLES2/gl2ext_api.in
+++ b/opengl/libs/GLES2/gl2ext_api.in
@@ -427,9 +427,6 @@
 void API_ENTRY(glDrawElementsInstancedBaseVertexEXT)(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei instancecount, GLint basevertex) {
     CALL_GL_API(glDrawElementsInstancedBaseVertexEXT, mode, count, type, indices, instancecount, basevertex);
 }
-void API_ENTRY(glMultiDrawElementsBaseVertexOES)(GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex) {
-    CALL_GL_API(glMultiDrawElementsBaseVertexOES, mode, count, type, indices, primcount, basevertex);
-}
 void API_ENTRY(glDrawArraysInstancedEXT)(GLenum mode, GLint start, GLsizei count, GLsizei primcount) {
     CALL_GL_API(glDrawArraysInstancedEXT, mode, start, count, primcount);
 }
diff --git a/opengl/libs/entries.in b/opengl/libs/entries.in
index a306510..4c1eefc 100644
--- a/opengl/libs/entries.in
+++ b/opengl/libs/entries.in
@@ -605,7 +605,6 @@
 GL_ENTRY(void, glMultiDrawArraysEXT, GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount)
 GL_ENTRY(void, glMultiDrawArraysIndirectEXT, GLenum mode, const void *indirect, GLsizei drawcount, GLsizei stride)
 GL_ENTRY(void, glMultiDrawElementsBaseVertexEXT, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex)
-GL_ENTRY(void, glMultiDrawElementsBaseVertexOES, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount, const GLint *basevertex)
 GL_ENTRY(void, glMultiDrawElementsEXT, GLenum mode, const GLsizei *count, GLenum type, const void *const*indices, GLsizei primcount)
 GL_ENTRY(void, glMultiDrawElementsIndirectEXT, GLenum mode, GLenum type, const void *indirect, GLsizei drawcount, GLsizei stride)
 GL_ENTRY(void, glMultiTexCoord4f, GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q)
diff --git a/opengl/libs/platform_entries.in b/opengl/libs/platform_entries.in
index 4673411..004aa5a 100644
--- a/opengl/libs/platform_entries.in
+++ b/opengl/libs/platform_entries.in
@@ -24,7 +24,7 @@
 EGL_ENTRY(EGLBoolean, eglWaitNative, EGLint)
 EGL_ENTRY(EGLint, eglGetError, void)
 EGL_ENTRY(__eglMustCastToProperFunctionPointerType, eglGetProcAddress, const char*)
-EGL_ENTRY(EGLBoolean, eglSwapBuffersWithDamageKHR, EGLDisplay, EGLSurface, EGLint*, EGLint)
+EGL_ENTRY(EGLBoolean, eglSwapBuffersWithDamageKHR, EGLDisplay, EGLSurface, const EGLint*, EGLint)
 EGL_ENTRY(EGLBoolean, eglSwapBuffers, EGLDisplay, EGLSurface)
 EGL_ENTRY(EGLBoolean, eglCopyBuffers, EGLDisplay, EGLSurface, NativePixmapType)
 EGL_ENTRY(const char*, eglQueryString, EGLDisplay, EGLint)
diff --git a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
index 7c255ed..5ba72af 100644
--- a/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
+++ b/opengl/tools/glgen/stubs/egl/eglCreateWindowSurface.cpp
@@ -113,7 +113,7 @@
     if (producer == NULL)
         goto not_valid_surface;
 
-    window = new android::Surface(producer, true);
+    window = android::sp<android::Surface>::make(producer, true);
 
     if (window == NULL)
         goto not_valid_surface;
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index 3ef5049..da1aae2 100644
--- a/services/audiomanager/IAudioManager.cpp
+++ b/services/audiomanager/IAudioManager.cpp
@@ -152,6 +152,12 @@
         data.writeNullableParcelable(extras);
         return remote()->transact(PORT_EVENT, data, &reply, IBinder::FLAG_ONEWAY);
     }
+
+    virtual status_t permissionUpdateBarrier() {
+        Parcel data, reply;
+        data.writeInterfaceToken(IAudioManager::getInterfaceDescriptor());
+        return remote()->transact(PERMISSION_UPDATE_BARRIER, data, &reply, 0);
+    }
 };
 
 IMPLEMENT_META_INTERFACE(AudioManager, "android.media.IAudioService");
diff --git a/services/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 72bd292..b63e919 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -23,6 +23,11 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+vintf_fragment {
+    name: "manifest_android.frameworks.automotive.display@1.0.xml",
+    src: "manifest_android.frameworks.automotive.display@1.0.xml",
+}
+
 cc_binary {
     name: "android.frameworks.automotive.display@1.0-service",
     defaults: ["hidl_defaults"],
@@ -50,7 +55,7 @@
         "-DLOG_TAG=\"AutomotiveDisplayService\""
     ],
 
-    vintf_fragments: [
+    vintf_fragment_modules: [
         "manifest_android.frameworks.automotive.display@1.0.xml",
     ],
 }
diff --git a/services/displayservice/OWNERS b/services/displayservice/OWNERS
index 7a3e4c2..40164aa 100644
--- a/services/displayservice/OWNERS
+++ b/services/displayservice/OWNERS
@@ -1,2 +1 @@
 smoreland@google.com
-lpy@google.com
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index a481c62..fadb1fd 100644
--- a/services/gpuservice/GpuService.cpp
+++ b/services/gpuservice/GpuService.cpp
@@ -100,6 +100,12 @@
     mGpuStats->insertTargetStatsArray(appPackageName, driverVersionCode, stats, values, valueCount);
 }
 
+void GpuService::addVulkanEngineName(const std::string& appPackageName,
+                                     const uint64_t driverVersionCode,
+                                     const char* engineName) {
+    mGpuStats->addVulkanEngineName(appPackageName, driverVersionCode, engineName);
+}
+
 void GpuService::toggleAngleAsSystemDriver(bool enabled) {
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
diff --git a/services/gpuservice/OWNERS b/services/gpuservice/OWNERS
index 07c681f..a3afca5 100644
--- a/services/gpuservice/OWNERS
+++ b/services/gpuservice/OWNERS
@@ -1,7 +1,4 @@
 chrisforbes@google.com
-lpy@google.com
-alecmouri@google.com
-lfy@google.com
-paulthomson@google.com
-pbaiget@google.com
-kocdemir@google.com
+tomnom@google.com
+
+alecmouri@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp
index 11b636d..6d758bc 100644
--- a/services/gpuservice/gpustats/GpuStats.cpp
+++ b/services/gpuservice/gpustats/GpuStats.cpp
@@ -181,6 +181,33 @@
     return insertTargetStatsArray(appPackageName, driverVersionCode, stats, &value, 1);
 }
 
+void GpuStats::addVulkanEngineName(const std::string& appPackageName,
+                                   const uint64_t driverVersionCode,
+                                   const char* engineNameCStr) {
+    ATRACE_CALL();
+
+    const std::string appStatsKey = appPackageName + std::to_string(driverVersionCode);
+    const size_t engineNameLen = std::min(strlen(engineNameCStr),
+                                          GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAME_LENGTH);
+    const std::string engineName{engineNameCStr, engineNameLen};
+
+    std::lock_guard<std::mutex> lock(mLock);
+    registerStatsdCallbacksIfNeeded();
+
+    const auto foundApp = mAppStats.find(appStatsKey);
+    if (foundApp == mAppStats.end()) {
+        return;
+    }
+
+    // Storing in std::set<> is not efficient for serialization tasks. Use
+    // vector instead and filter out dups
+    std::vector<std::string>& engineNames = foundApp->second.vulkanEngineNames;
+    if (engineNames.size() < GpuStatsAppInfo::MAX_VULKAN_ENGINE_NAMES
+        && std::find(engineNames.cbegin(), engineNames.cend(), engineName) == engineNames.cend()) {
+        engineNames.push_back(engineName);
+    }
+}
+
 void GpuStats::insertTargetStatsArray(const std::string& appPackageName,
                                  const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats,
                                  const uint64_t* values, const uint32_t valueCount) {
@@ -389,6 +416,11 @@
             std::string angleDriverBytes = int64VectorToProtoByteString(
                 ele.second.angleDriverLoadingTime);
 
+            std::vector<const char*> engineNames;
+            for (const std::string &engineName : ele.second.vulkanEngineNames) {
+                engineNames.push_back(engineName.c_str());
+            }
+
             android::util::addAStatsEvent(
                     data,
                     android::util::GPU_STATS_APP_INFO,
@@ -410,7 +442,8 @@
                     ele.second.vulkanApiVersion,
                     ele.second.vulkanDeviceFeaturesEnabled,
                     ele.second.vulkanInstanceExtensions,
-                    ele.second.vulkanDeviceExtensions);
+                    ele.second.vulkanDeviceExtensions,
+                    engineNames);
         }
     }
 
diff --git a/services/gpuservice/gpustats/include/gpustats/GpuStats.h b/services/gpuservice/gpustats/include/gpustats/GpuStats.h
index 22c64db..961f011 100644
--- a/services/gpuservice/gpustats/include/gpustats/GpuStats.h
+++ b/services/gpuservice/gpustats/include/gpustats/GpuStats.h
@@ -44,6 +44,9 @@
     void insertTargetStatsArray(const std::string& appPackageName,
                            const uint64_t driverVersionCode, const GpuStatsInfo::Stats stats,
                            const uint64_t* values, const uint32_t valueCount);
+    // Add the engine name passed in VkApplicationInfo during CreateInstance
+    void addVulkanEngineName(const std::string& appPackageName,
+                             const uint64_t driverVersionCode, const char* engineName);
     // dumpsys interface
     void dump(const Vector<String16>& args, std::string* result);
 
diff --git a/services/gpuservice/gpuwork/GpuWork.cpp b/services/gpuservice/gpuwork/GpuWork.cpp
index 1a744ab..7628745 100644
--- a/services/gpuservice/gpuwork/GpuWork.cpp
+++ b/services/gpuservice/gpuwork/GpuWork.cpp
@@ -44,7 +44,7 @@
 
 #include "gpuwork/gpuWork.h"
 
-#define ONE_MS_IN_NS (10000000)
+#define MSEC_PER_NSEC (1000LU * 1000LU)
 
 namespace android {
 namespace gpuwork {
@@ -118,6 +118,9 @@
 }
 
 void GpuWork::initialize() {
+    // Workaround b/347947040 by allowing time for statsd / bpf setup.
+    std::this_thread::sleep_for(std::chrono::seconds(30));
+
     // Make sure BPF programs are loaded.
     bpf::waitForProgsLoaded();
 
@@ -382,10 +385,11 @@
     ALOGI("pullWorkAtoms: after random selection: uids.size() == %zu", uids.size());
 
     auto now = std::chrono::steady_clock::now();
-    long long duration =
-            std::chrono::duration_cast<std::chrono::seconds>(now - mPreviousMapClearTimePoint)
-                    .count();
-    if (duration > std::numeric_limits<int32_t>::max() || duration < 0) {
+    int32_t duration =
+            static_cast<int32_t>(
+                std::chrono::duration_cast<std::chrono::seconds>(now - mPreviousMapClearTimePoint)
+                    .count());
+    if (duration < 0) {
         // This is essentially impossible. If it does somehow happen, give up,
         // but still clear the map.
         clearMap();
@@ -401,13 +405,14 @@
             }
             const UidTrackingInfo& info = it->second;
 
-            uint64_t total_active_duration_ms = info.total_active_duration_ns / ONE_MS_IN_NS;
-            uint64_t total_inactive_duration_ms = info.total_inactive_duration_ns / ONE_MS_IN_NS;
+            int32_t total_active_duration_ms =
+                static_cast<int32_t>(info.total_active_duration_ns / MSEC_PER_NSEC);
+            int32_t total_inactive_duration_ms =
+                static_cast<int32_t>(info.total_inactive_duration_ns / MSEC_PER_NSEC);
 
             // Skip this atom if any numbers are out of range. |duration| is
             // already checked above.
-            if (total_active_duration_ms > std::numeric_limits<int32_t>::max() ||
-                total_inactive_duration_ms > std::numeric_limits<int32_t>::max()) {
+            if (total_active_duration_ms < 0 || total_inactive_duration_ms < 0) {
                 continue;
             }
 
@@ -418,11 +423,11 @@
                                           // gpu_id
                                           bitcast_int32(gpuId),
                                           // time_duration_seconds
-                                          static_cast<int32_t>(duration),
+                                          duration,
                                           // total_active_duration_millis
-                                          static_cast<int32_t>(total_active_duration_ms),
+                                          total_active_duration_ms,
                                           // total_inactive_duration_millis
-                                          static_cast<int32_t>(total_inactive_duration_ms));
+                                          total_inactive_duration_ms);
         }
     }
     clearMap();
diff --git a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
index f470189..94abc69 100644
--- a/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
+++ b/services/gpuservice/gpuwork/bpfprogs/gpuWork.c
@@ -20,11 +20,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
-#ifdef MOCK_BPF
-#include <test/mock_bpf_helpers.h>
-#else
 #include <bpf_helpers.h>
-#endif
 
 #define S_IN_NS (1000000000)
 #define SMALL_TIME_GAP_LIMIT_NS (S_IN_NS)
diff --git a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
index e70da54..60cd2c7 100644
--- a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
+++ b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
@@ -125,7 +125,7 @@
     static constexpr size_t kNumGpusHardLimit = 32;
 
     // The minimum GPU time needed to actually log stats for a UID.
-    static constexpr uint64_t kMinGpuTimeNanoseconds = 30U * 1000000000U; // 30 seconds.
+    static constexpr uint64_t kMinGpuTimeNanoseconds = 10LLU * 1000000000LLU; // 10 seconds.
 
     // The previous time point at which |mGpuWorkMap| was cleared.
     std::chrono::steady_clock::time_point mPreviousMapClearTimePoint GUARDED_BY(mMutex);
diff --git a/services/gpuservice/include/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h
index 54f8f66..3072885 100644
--- a/services/gpuservice/include/gpuservice/GpuService.h
+++ b/services/gpuservice/include/gpuservice/GpuService.h
@@ -64,6 +64,8 @@
     void setUpdatableDriverPath(const std::string& driverPath) override;
     std::string getUpdatableDriverPath() override;
     void toggleAngleAsSystemDriver(bool enabled) override;
+    void addVulkanEngineName(const std::string& appPackageName, const uint64_t driverVersionCode,
+                             const char *engineName) override;
 
     /*
      * IBinder interface
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-0 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-0
new file mode 100644
index 0000000..120a34d
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-0
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-1 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-1
new file mode 100644
index 0000000..92d5bdf
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-1
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-10 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-10
new file mode 100644
index 0000000..c044c84
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-10
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-11 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-11
new file mode 100644
index 0000000..430552e
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-11
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-12 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-12
new file mode 100644
index 0000000..f7849bb
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-12
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-13 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-13
new file mode 100644
index 0000000..2f0a655
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-13
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-14 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-14
new file mode 100644
index 0000000..bd8fb01
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-14
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-15 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-15
new file mode 100644
index 0000000..29aa2b1
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-15
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-16 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-16
new file mode 100644
index 0000000..e00100f
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-16
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-17 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-17
new file mode 100644
index 0000000..db281e1
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-17
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-18 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-18
new file mode 100644
index 0000000..5592c32
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-18
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-2 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-2
new file mode 100644
index 0000000..5bd2b99
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-2
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-3 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-3
new file mode 100644
index 0000000..2d89289
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-3
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-4 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-4
new file mode 100644
index 0000000..8f3f1c2
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-4
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-5 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-5
new file mode 100644
index 0000000..95b05e7
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-5
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-6 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-6
new file mode 100644
index 0000000..497a501
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-6
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-7 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-7
new file mode 100644
index 0000000..9902bde
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-7
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-8 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-8
new file mode 100644
index 0000000..fdaaf31
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-8
Binary files differ
diff --git a/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-9 b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-9
new file mode 100644
index 0000000..a197c6a
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/corpus/seed-2024-08-29-9
Binary files differ
diff --git a/services/gpuservice/tests/unittests/GpuStatsTest.cpp b/services/gpuservice/tests/unittests/GpuStatsTest.cpp
index 4ce533f..b367457 100644
--- a/services/gpuservice/tests/unittests/GpuStatsTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuStatsTest.cpp
@@ -46,6 +46,8 @@
 #define UPDATED_DRIVER_VER_CODE   1
 #define UPDATED_DRIVER_BUILD_TIME 234
 #define VULKAN_VERSION            345
+#define VULKAN_ENGINE_NAME_1      "testVulkanEngine1"
+#define VULKAN_ENGINE_NAME_2      "testVulkanEngine2"
 #define APP_PKG_NAME_1            "testapp1"
 #define APP_PKG_NAME_2            "testapp2"
 #define DRIVER_LOADING_TIME_1     678
@@ -243,6 +245,8 @@
     mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
                                  GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                  VULKAN_DEVICE_EXTENSION_1);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                             VULKAN_ENGINE_NAME_1);
 
     EXPECT_TRUE(inputCommand(InputCommand::DUMP_APP).empty());
 }
@@ -282,6 +286,8 @@
     mGpuStats->insertTargetStats(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
                                  GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                  VULKAN_DEVICE_EXTENSION_2);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                             VULKAN_ENGINE_NAME_1);
 
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1"));
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("falsePrerotation = 1"));
@@ -302,6 +308,64 @@
     expectedResult.str("");
     expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1
                     << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2;
+    expectedResult.str("");
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ",";
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+// Verify the vulkanEngineNames list behaves like a set and dedupes additions
+TEST_F(GpuStatsTest, vulkanEngineNamesBehavesLikeSet) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+    for (int i = 0; i < 4; i++) {
+        mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                       VULKAN_ENGINE_NAME_1);
+    }
+
+    std::stringstream wrongResult, expectedResult;
+    wrongResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", " <<
+                   VULKAN_ENGINE_NAME_1;
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1;
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), Not(HasSubstr(wrongResult.str())));
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameAlone) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   "");
+
+    std::stringstream expectedResult;
+    expectedResult << "vulkanEngineNames: ,";
+
+    EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
+}
+
+TEST_F(GpuStatsTest, vulkanEngineNamesCheckEmptyEngineNameWithOthers) {
+    mGpuStats->insertDriverStats(BUILTIN_DRIVER_PKG_NAME, BUILTIN_DRIVER_VER_NAME,
+                                 BUILTIN_DRIVER_VER_CODE, BUILTIN_DRIVER_BUILD_TIME, APP_PKG_NAME_1,
+                                 VULKAN_VERSION, GpuStatsInfo::Driver::GL, true,
+                                 DRIVER_LOADING_TIME_1);
+
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   VULKAN_ENGINE_NAME_1);
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   "");
+    mGpuStats->addVulkanEngineName(APP_PKG_NAME_1, BUILTIN_DRIVER_VER_CODE,
+                                   VULKAN_ENGINE_NAME_2);
+
+    std::stringstream expectedResult;
+    expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", "
+                   << ", " <<  VULKAN_ENGINE_NAME_2;
+
     EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
 }
 
@@ -350,6 +414,10 @@
         mGpuStats->insertTargetStats(fullPkgName, BUILTIN_DRIVER_VER_CODE,
                                     GpuStatsInfo::Stats::VULKAN_DEVICE_EXTENSION,
                                     VULKAN_DEVICE_EXTENSION_2);
+        mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE,
+                                 VULKAN_ENGINE_NAME_1);
+        mGpuStats->addVulkanEngineName(fullPkgName, BUILTIN_DRIVER_VER_CODE,
+                                 VULKAN_ENGINE_NAME_2);
 
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(fullPkgName.c_str()));
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr("cpuVulkanInUse = 1"));
@@ -371,6 +439,9 @@
         expectedResult.str("");
         expectedResult << "vulkanDeviceExtensions: 0x" << std::hex << VULKAN_DEVICE_EXTENSION_1
                         << " 0x" << std::hex << VULKAN_DEVICE_EXTENSION_2;
+        expectedResult.str("");
+        expectedResult << "vulkanEngineNames: " << VULKAN_ENGINE_NAME_1 << ", "
+                       << VULKAN_ENGINE_NAME_2 << ",";
         EXPECT_THAT(inputCommand(InputCommand::DUMP_APP), HasSubstr(expectedResult.str()));
     }
 
diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp
index a24822a..6e0a9f7 100644
--- a/services/gpuservice/vts/Android.bp
+++ b/services/gpuservice/vts/Android.bp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 package {
+    default_team: "trendy_team_android_gpu",
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
diff --git a/services/gpuservice/vts/OWNERS b/services/gpuservice/vts/OWNERS
index e0a9725..a980866 100644
--- a/services/gpuservice/vts/OWNERS
+++ b/services/gpuservice/vts/OWNERS
@@ -1,7 +1,6 @@
 # Bug component: 653544
 kocdemir@google.com
 paulthomson@google.com
-pbaiget@google.com
 lfy@google.com
 chrisforbes@google.com
 alecmouri@google.com
diff --git a/services/gpuservice/vts/TEST_MAPPING b/services/gpuservice/vts/TEST_MAPPING
index b33e962..a809be1 100644
--- a/services/gpuservice/vts/TEST_MAPPING
+++ b/services/gpuservice/vts/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "GpuServiceVendorTests"
+      "name": "GpuServiceVendorTests",
+      "options": [
+        {
+          // Exclude test methods that require a physical device to run.
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+        }
+      ]
     }
   ]
 }
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 6c16335..5c12323 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -19,7 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import android.platform.test.annotations.RestrictedBuildTest;
+import android.platform.test.annotations.RequiresDevice;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -63,7 +63,7 @@
     }
 
     @VsrTest(requirements={"VSR-3.3-004"})
-    @RestrictedBuildTest
+    @RequiresDevice
     @Test
     public void testGpuWorkPeriodTracepointFormat() throws Exception {
         CommandResult commandResult = getDevice().executeShellV2Command(
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index d244b1a..ca92ab5 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -56,6 +56,16 @@
         host: {
             sanitize: {
                 address: true,
+                diag: {
+                    cfi: true,
+                    integer_overflow: true,
+                    memtag_heap: true,
+                    undefined: true,
+                    misc_undefined: [
+                        "bounds",
+                        "all",
+                    ],
+                },
             },
             include_dirs: [
                 "bionic/libc/kernel/android/uapi/",
@@ -107,6 +117,7 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
@@ -161,9 +172,8 @@
     export_static_lib_headers: [
         "libinputdispatcher",
     ],
-    export_include_dirs: [
-        ".",
-        "include",
+    export_shared_lib_headers: [
+        "libinputflinger_base",
     ],
 }
 
@@ -174,7 +184,16 @@
 cc_library_headers {
     name: "libinputflinger_headers",
     host_supported: true,
-    export_include_dirs: ["include"],
+    export_include_dirs: [
+        "include",
+        ".",
+    ],
+    header_libs: [
+        "libchrome-gestures_headers",
+    ],
+    export_header_lib_headers: [
+        "libchrome-gestures_headers",
+    ],
 }
 
 filegroup {
@@ -198,6 +217,7 @@
         "libcutils",
         "libinput",
         "liblog",
+        "libprocessgroup",
         "libstatslog",
         "libutils",
     ],
@@ -270,5 +290,6 @@
         "FrameworksServicesTests",
         "CtsSecurityTestCases",
         "CtsSecurityBulletinHostTestCases",
+        "monkey_test",
     ],
 }
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..e376734 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
 
 namespace android {
 
+const static ui::Transform kIdentityTransform;
+const static std::array<uint8_t, 32> kInvalidHmac{};
+
 static common::Source getSource(uint32_t source) {
     static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN,
                   "SOURCE_UNKNOWN mismatch");
@@ -311,7 +314,7 @@
     common::MotionEvent event;
     event.deviceId = args.deviceId;
     event.source = getSource(args.source);
-    event.displayId = args.displayId;
+    event.displayId = args.displayId.val();
     event.downTime = args.downTime;
     event.eventTime = args.eventTime;
     event.deviceTimestamp = 0;
@@ -337,4 +340,31 @@
     return event;
 }
 
+MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform,
+                          const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) {
+    if (transform == nullptr) transform = &kIdentityTransform;
+    if (rawTransform == nullptr) rawTransform = &kIdentityTransform;
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    MotionEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.actionButton, args.flags, args.edgeFlags, args.metaState,
+                     args.buttonState, args.classification, *transform, args.xPrecision,
+                     args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform,
+                     args.downTime, args.eventTime, args.getPointerCount(),
+                     args.pointerProperties.data(), args.pointerCoords.data());
+    return event;
+}
+
+KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount,
+                    const std::array<uint8_t, 32>* hmac) {
+    if (hmac == nullptr) hmac = &kInvalidHmac;
+
+    KeyEvent event;
+    event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action,
+                     args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount,
+                     args.downTime, args.eventTime);
+    return event;
+}
+
 } // namespace android
diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h
index 4d3b768..0d4cbb0 100644
--- a/services/inputflinger/InputCommonConverter.h
+++ b/services/inputflinger/InputCommonConverter.h
@@ -16,16 +16,25 @@
 
 #pragma once
 
+#include "InputListener.h"
+
 #include <aidl/android/hardware/input/common/Axis.h>
 #include <aidl/android/hardware/input/common/MotionEvent.h>
-#include "InputListener.h"
+#include <input/Input.h>
 
 namespace android {
 
-/**
- * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent
- */
+/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */
 ::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent(
         const NotifyMotionArgs& args);
 
+/** Convert from NotifyMotionArgs to MotionEvent. */
+MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr,
+                          const ui::Transform* rawTransform = nullptr,
+                          const std::array<uint8_t, 32>* hmac = nullptr);
+
+/** Convert from NotifyKeyArgs to KeyEvent. */
+KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0,
+                    const std::array<uint8_t, 32>* hmac = nullptr);
+
 } // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
index b5cb3cb..4621144 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.cpp
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -133,15 +133,6 @@
     mNextListener.notify(args);
 }
 
-void InputDeviceMetricsCollector::notifyConfigurationChanged(
-        const NotifyConfigurationChangedArgs& args) {
-    {
-        std::scoped_lock lock(mLock);
-        reportCompletedSessions();
-    }
-    mNextListener.notify(args);
-}
-
 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
     {
         std::scoped_lock lock(mLock);
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
index 1bcd527..0a520e6 100644
--- a/services/inputflinger/InputDeviceMetricsCollector.h
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -107,7 +107,6 @@
                                 std::chrono::nanoseconds usageSessionTimeout);
 
     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;
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
index 1ada5e5..2ef94fb 100644
--- a/services/inputflinger/InputFilter.cpp
+++ b/services/inputflinger/InputFilter.cpp
@@ -32,7 +32,7 @@
     event.eventTime = args.eventTime;
     event.deviceId = args.deviceId;
     event.source = static_cast<Source>(args.source);
-    event.displayId = args.displayId;
+    event.displayId = args.displayId.val();
     event.policyFlags = args.policyFlags;
     event.action = static_cast<KeyEventAction>(args.action);
     event.flags = args.flags;
@@ -60,6 +60,7 @@
         AidlDeviceInfo& aidlInfo = mDeviceInfos.emplace_back();
         aidlInfo.deviceId = info.getId();
         aidlInfo.external = info.isExternal();
+        aidlInfo.keyboardType = info.getKeyboardType();
     }
     if (isFilterEnabled()) {
         LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(mDeviceInfos).isOk());
@@ -67,10 +68,6 @@
     mNextListener.notify(args);
 }
 
-void InputFilter::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    mNextListener.notify(args);
-}
-
 void InputFilter::notifyKey(const NotifyKeyArgs& args) {
     if (isFilterEnabled()) {
         LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
@@ -149,6 +146,12 @@
 
 void InputFilter::dump(std::string& dump) {
     dump += "InputFilter:\n";
+    if (isFilterEnabled()) {
+        std::string result;
+        LOG_ALWAYS_FATAL_IF(!mInputFilterRust->dumpFilter(&result).isOk());
+        dump += result;
+        dump += "\n";
+    }
 }
 
 } // namespace android
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
index 4ddc9f4..f626703 100644
--- a/services/inputflinger/InputFilter.h
+++ b/services/inputflinger/InputFilter.h
@@ -53,7 +53,6 @@
                          InputFilterPolicyInterface& policy);
     ~InputFilter() override = default;
     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;
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 6c31442..5fbdc84 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -19,9 +19,10 @@
 #include "InputFilterCallbacks.h"
 #include <aidl/com/android/server/inputflinger/BnInputThread.h>
 #include <android/binder_auto_utils.h>
+#include <utils/Looper.h>
 #include <utils/StrongPointer.h>
-#include <utils/Thread.h>
 #include <functional>
+#include "InputThread.h"
 
 namespace android {
 
@@ -29,45 +30,46 @@
 
 NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
     return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
-                         static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
-                         static_cast<int32_t>(event.action), event.flags, event.keyCode,
-                         event.scanCode, event.metaState, event.downTime);
+                         static_cast<uint32_t>(event.source), ui::LogicalDisplayId{event.displayId},
+                         event.policyFlags, static_cast<int32_t>(event.action), event.flags,
+                         event.keyCode, event.scanCode, event.metaState, event.downTime);
 }
 
 namespace {
 
 using namespace aidl::com::android::server::inputflinger;
 
-class InputFilterThreadImpl : public Thread {
-public:
-    explicit InputFilterThreadImpl(std::function<void()> loop)
-          : Thread(/*canCallJava=*/true), mThreadLoop(loop) {}
-
-    ~InputFilterThreadImpl() {}
-
-private:
-    std::function<void()> mThreadLoop;
-
-    bool threadLoop() override {
-        mThreadLoop();
-        return true;
-    }
-};
-
 class InputFilterThread : public BnInputThread {
 public:
     InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
-        mThread = sp<InputFilterThreadImpl>::make([this]() { loopOnce(); });
-        mThread->run("InputFilterThread", ANDROID_PRIORITY_URGENT_DISPLAY);
+        mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
+        mThread = std::make_unique<InputThread>(
+                "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
     }
 
     ndk::ScopedAStatus finish() override {
-        mThread->requestExit();
+        if (mThread && mThread->isCallingThread()) {
+            ALOGE("InputFilterThread cannot be stopped on itself!");
+            return ndk::ScopedAStatus::fromStatus(INVALID_OPERATION);
+        }
+        mThread.reset();
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus sleepUntil(nsecs_t when) override {
+        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+        mLooper->pollOnce(toMillisecondTimeoutDelay(now, when));
+        return ndk::ScopedAStatus::ok();
+    }
+
+    ndk::ScopedAStatus wake() override {
+        mLooper->wake();
         return ndk::ScopedAStatus::ok();
     }
 
 private:
-    sp<Thread> mThread;
+    sp<Looper> mLooper;
+    std::unique_ptr<InputThread> mThread;
     std::shared_ptr<IInputThreadCallback> mCallback;
 
     void loopOnce() { LOG_ALWAYS_FATAL_IF(!mCallback->loopOnce().isOk()); }
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index 016ae04..8b6accf 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -47,7 +47,6 @@
 void InputListenerInterface::notify(const NotifyArgs& generalArgs) {
     Visitor v{
             [&](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); },
@@ -68,10 +67,6 @@
     mArgsQueue.emplace_back(args);
 }
 
-void QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    mArgsQueue.emplace_back(args);
-}
-
 void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {
     mArgsQueue.emplace_back(args);
 }
@@ -119,13 +114,6 @@
     mInnerListener.notify(args);
 }
 
-void TracedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    constexpr static auto& fnName = __func__;
-    ATRACE_NAME_IF(ATRACE_ENABLED(),
-                   StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
-    mInnerListener.notify(args);
-}
-
 void TracedInputListener::notifyKey(const NotifyKeyArgs& args) {
     constexpr static auto& fnName = __func__;
     ATRACE_NAME_IF(ATRACE_ENABLED(),
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index ae066c0..b155122 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -41,7 +41,6 @@
 const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
         sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
 
-const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
 const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
 
 int32_t exceptionCodeFromStatusT(status_t status) {
@@ -152,12 +151,10 @@
     mTracingStages.emplace_back(
             std::make_unique<TracedInputListener>("InputProcessor", *mProcessor));
 
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mChoreographer =
-                std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
-        mTracingStages.emplace_back(
-                std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
-    }
+    mChoreographer =
+            std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
+    mTracingStages.emplace_back(
+            std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
 
     mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mTracingStages.back());
     mTracingStages.emplace_back(
@@ -245,16 +242,18 @@
     dump += '\n';
     mBlocker->dump(dump);
     dump += '\n';
-    if (ENABLE_POINTER_CHOREOGRAPHER) {
-        mChoreographer->dump(dump);
-        dump += '\n';
-    }
+    mChoreographer->dump(dump);
+    dump += '\n';
     mProcessor->dump(dump);
     dump += '\n';
     if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
         mCollector->dump(dump);
         dump += '\n';
     }
+    if (ENABLE_INPUT_FILTER_RUST) {
+        mInputFilter->dump(dump);
+        dump += '\n';
+    }
     mDispatcher->dump(dump);
     dump += '\n';
 }
diff --git a/services/inputflinger/InputProcessor.cpp b/services/inputflinger/InputProcessor.cpp
index 6dd267c..8b8b1ad 100644
--- a/services/inputflinger/InputProcessor.cpp
+++ b/services/inputflinger/InputProcessor.cpp
@@ -419,12 +419,6 @@
     mQueuedListener.flush();
 }
 
-void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    // pass through
-    mQueuedListener.notifyConfigurationChanged(args);
-    mQueuedListener.flush();
-}
-
 void InputProcessor::notifyKey(const NotifyKeyArgs& args) {
     // pass through
     mQueuedListener.notifyKey(args);
diff --git a/services/inputflinger/InputProcessor.h b/services/inputflinger/InputProcessor.h
index 7a00a2d..2945dd2 100644
--- a/services/inputflinger/InputProcessor.h
+++ b/services/inputflinger/InputProcessor.h
@@ -246,7 +246,6 @@
     explicit InputProcessor(InputListenerInterface& listener);
 
     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;
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index 4ec5b89..6b2627c 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -68,7 +68,7 @@
         if (currentViewport.type == type) {
             if (!result ||
                 (type == ViewportType::INTERNAL &&
-                 currentViewport.displayId == ADISPLAY_ID_DEFAULT)) {
+                 currentViewport.displayId == ui::LogicalDisplayId::DEFAULT)) {
                 result = std::make_optional(currentViewport);
             }
             count++;
@@ -93,7 +93,7 @@
 }
 
 std::optional<DisplayViewport> InputReaderConfiguration::getDisplayViewportById(
-        int32_t displayId) const {
+        ui::LogicalDisplayId displayId) const {
     for (const DisplayViewport& currentViewport : mDisplays) {
         if (currentViewport.displayId == displayId) {
             return std::make_optional(currentViewport);
diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp
index e74f258..449eb45 100644
--- a/services/inputflinger/InputThread.cpp
+++ b/services/inputflinger/InputThread.cpp
@@ -16,8 +16,14 @@
 
 #include "InputThread.h"
 
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <processgroup/processgroup.h>
+
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 namespace {
 
 // Implementation of Thread from libutils.
@@ -43,6 +49,11 @@
       : mName(name), mThreadWake(wake) {
     mThread = sp<InputThreadImpl>::make(loop);
     mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
+    if (input_flags::enable_input_policy_profile()) {
+        if (!applyInputEventProfile()) {
+            LOG(ERROR) << "Couldn't apply input policy profile for " << name;
+        }
+    }
 }
 
 InputThread::~InputThread() {
@@ -63,4 +74,14 @@
 #endif
 }
 
+bool InputThread::applyInputEventProfile() {
+#if defined(__ANDROID__)
+    return SetTaskProfiles(mThread->getTid(), {"InputPolicy"});
+#else
+    // Since thread information is not available and there's no benefit of
+    // applying the task profile on host, return directly.
+    return true;
+#endif
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
index de836e9..b2680a2 100644
--- a/services/inputflinger/NotifyArgs.cpp
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -35,15 +35,10 @@
                                                              std::vector<InputDeviceInfo> infos)
       : id(id), inputDeviceInfos(std::move(infos)) {}
 
-// --- NotifyConfigurationChangedArgs ---
-
-NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime)
-      : id(id), eventTime(eventTime) {}
-
 // --- NotifyKeyArgs ---
 
 NotifyKeyArgs::NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                             uint32_t source, int32_t displayId, uint32_t policyFlags,
+                             uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
                              int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
                              int32_t metaState, nsecs_t downTime)
       : id(id),
@@ -64,7 +59,7 @@
 
 NotifyMotionArgs::NotifyMotionArgs(
         int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId, uint32_t source,
-        int32_t displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
+        ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action, int32_t actionButton,
         int32_t flags, int32_t metaState, int32_t buttonState, MotionClassification classification,
         int32_t edgeFlags, uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
@@ -198,7 +193,6 @@
 const char* toString(const NotifyArgs& args) {
     Visitor toStringVisitor{
             [&](const NotifyInputDevicesChangedArgs&) { return "NotifyInputDevicesChangedArgs"; },
-            [&](const NotifyConfigurationChangedArgs&) { return "NotifyConfigurationChangedArgs"; },
             [&](const NotifyKeyArgs&) { return "NotifyKeyArgs"; },
             [&](const NotifyMotionArgs&) { return "NotifyMotionArgs"; },
             [&](const NotifySensorArgs&) { return "NotifySensorArgs"; },
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 36e133b..006d507 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -17,7 +17,13 @@
 #define LOG_TAG "PointerChoreographer"
 
 #include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#if defined(__ANDROID__)
+#include <gui/SurfaceComposerClient.h>
+#endif
+#include <input/Keyboard.h>
 #include <input/PrintTools.h>
+#include <unordered_set>
 
 #include "PointerChoreographer.h"
 
@@ -58,8 +64,9 @@
              !isFromSource(sources, AINPUT_SOURCE_STYLUS));
 }
 
-inline void notifyPointerDisplayChange(std::optional<std::tuple<int32_t, FloatPoint>> change,
-                                       PointerChoreographerPolicyInterface& policy) {
+inline void notifyPointerDisplayChange(
+        std::optional<std::tuple<ui::LogicalDisplayId, FloatPoint>> change,
+        PointerChoreographerPolicyInterface& policy) {
     if (!change) {
         return;
     }
@@ -79,22 +86,70 @@
     }
 }
 
+// filters and returns a set of privacy sensitive displays that are currently visible.
+std::unordered_set<ui::LogicalDisplayId> getPrivacySensitiveDisplaysFromWindowInfos(
+        const std::vector<gui::WindowInfo>& windowInfos) {
+    std::unordered_set<ui::LogicalDisplayId> privacySensitiveDisplays;
+    for (const auto& windowInfo : windowInfos) {
+        if (!windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE) &&
+            windowInfo.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY)) {
+            privacySensitiveDisplays.insert(windowInfo.displayId);
+        }
+    }
+    return privacySensitiveDisplays;
+}
+
 } // namespace
 
 // --- PointerChoreographer ---
 
-PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
+PointerChoreographer::PointerChoreographer(InputListenerInterface& inputListener,
                                            PointerChoreographerPolicyInterface& policy)
+      : PointerChoreographer(
+                inputListener, policy,
+                [](const sp<android::gui::WindowInfosListener>& listener) {
+                    auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{},
+                                                      std::vector<android::gui::DisplayInfo>{});
+#if defined(__ANDROID__)
+                    SurfaceComposerClient::getDefault()->addWindowInfosListener(listener,
+                                                                                &initialInfo);
+#endif
+                    return initialInfo.first;
+                },
+                [](const sp<android::gui::WindowInfosListener>& listener) {
+#if defined(__ANDROID__)
+                    SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
+#endif
+                }) {
+}
+
+PointerChoreographer::PointerChoreographer(
+        android::InputListenerInterface& listener,
+        android::PointerChoreographerPolicyInterface& policy,
+        const android::PointerChoreographer::WindowListenerRegisterConsumer& registerListener,
+        const android::PointerChoreographer::WindowListenerUnregisterConsumer& unregisterListener)
       : mTouchControllerConstructor([this]() {
             return mPolicy.createPointerController(
                     PointerControllerInterface::ControllerType::TOUCH);
         }),
         mNextListener(listener),
         mPolicy(policy),
-        mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
-        mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+        mDefaultMouseDisplayId(ui::LogicalDisplayId::DEFAULT),
+        mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
         mShowTouchesEnabled(false),
-        mStylusPointerIconEnabled(false) {}
+        mStylusPointerIconEnabled(false),
+        mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
+        mRegisterListener(registerListener),
+        mUnregisterListener(unregisterListener) {}
+
+PointerChoreographer::~PointerChoreographer() {
+    std::scoped_lock _l(mLock);
+    if (mWindowInfoListener == nullptr) {
+        return;
+    }
+    mWindowInfoListener->onPointerChoreographerDestroyed();
+    mUnregisterListener(mWindowInfoListener);
+}
 
 void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
     PointerDisplayChange pointerDisplayChange;
@@ -110,11 +165,8 @@
     mNextListener.notify(args);
 }
 
-void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    mNextListener.notify(args);
-}
-
 void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+    fadeMouseCursorOnKeyPress(args);
     mNextListener.notify(args);
 }
 
@@ -124,6 +176,33 @@
     mNextListener.notify(newArgs);
 }
 
+void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs& args) {
+    if (args.action == AKEY_EVENT_ACTION_UP || isMetaKey(args.keyCode)) {
+        return;
+    }
+    // Meta state for these keys is ignored for dismissing cursor while typing
+    constexpr static int32_t ALLOW_FADING_META_STATE_MASK = AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON |
+            AMETA_SCROLL_LOCK_ON | AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON;
+    if (args.metaState & ~ALLOW_FADING_META_STATE_MASK) {
+        // Do not fade if any other meta state is active
+        return;
+    }
+    if (!mPolicy.isInputMethodConnectionActive()) {
+        return;
+    }
+
+    std::scoped_lock _l(mLock);
+    ui::LogicalDisplayId targetDisplay = args.displayId;
+    if (targetDisplay == ui::LogicalDisplayId::INVALID) {
+        targetDisplay = mCurrentFocusedDisplay;
+    }
+    auto it = mMousePointersByDisplay.find(targetDisplay);
+    if (it != mMousePointersByDisplay.end()) {
+        mPolicy.notifyMouseCursorFadedOnTyping();
+        it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+    }
+}
+
 NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
     std::scoped_lock _l(mLock);
 
@@ -147,26 +226,39 @@
                    << args.dump();
     }
 
+    mMouseDevices.emplace(args.deviceId);
     auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
+    NotifyMotionArgs newArgs(args);
+    newArgs.displayId = displayId;
 
-    const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    pc.move(deltaX, deltaY);
+    if (MotionEvent::isValidCursorPosition(args.xCursorPosition, args.yCursorPosition)) {
+        // This is an absolute mouse device that knows about the location of the cursor on the
+        // display, so set the cursor position to the specified location.
+        const auto [x, y] = pc.getPosition();
+        const float deltaX = args.xCursorPosition - x;
+        const float deltaY = args.yCursorPosition - y;
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
+        pc.setPosition(args.xCursorPosition, args.yCursorPosition);
+    } else {
+        // This is a relative mouse, so move the cursor by the specified amount.
+        const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+        const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        pc.move(deltaX, deltaY);
+        const auto [x, y] = pc.getPosition();
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+        newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+        newArgs.xCursorPosition = x;
+        newArgs.yCursorPosition = y;
+    }
     if (canUnfadeOnDisplay(displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
-
-    const auto [x, y] = pc.getPosition();
-    NotifyMotionArgs newArgs(args);
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
-    newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-    newArgs.xCursorPosition = x;
-    newArgs.yCursorPosition = y;
-    newArgs.displayId = displayId;
     return newArgs;
 }
 
 NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+    mMouseDevices.emplace(args.deviceId);
     auto [displayId, pc] = ensureMouseControllerLocked(args.displayId);
 
     NotifyMotionArgs newArgs(args);
@@ -205,7 +297,7 @@
 }
 
 void PointerChoreographer::processDrawingTabletEventLocked(const android::NotifyMotionArgs& args) {
-    if (args.displayId == ADISPLAY_ID_NONE) {
+    if (args.displayId == ui::LogicalDisplayId::INVALID) {
         return;
     }
 
@@ -215,9 +307,13 @@
     }
 
     // Use a mouse pointer controller for drawing tablets, or create one if it doesn't exist.
-    auto [it, _] = mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
-                                                              getMouseControllerConstructor(
-                                                                      args.displayId));
+    auto [it, controllerAdded] =
+            mDrawingTabletPointersByDevice.try_emplace(args.deviceId,
+                                                       getMouseControllerConstructor(
+                                                               args.displayId));
+    if (controllerAdded) {
+        onControllerAddedOrRemovedLocked();
+    }
 
     PointerControllerInterface& pc = *it->second;
 
@@ -241,7 +337,7 @@
  * For touch events, we do not need to populate the cursor position.
  */
 void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
-    if (args.displayId == ADISPLAY_ID_NONE) {
+    if (!args.displayId.isValid()) {
         return;
     }
 
@@ -255,7 +351,11 @@
     }
 
     // Get the touch pointer controller for the device, or create one if it doesn't exist.
-    auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    auto [it, controllerAdded] =
+            mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+    if (controllerAdded) {
+        onControllerAddedOrRemovedLocked();
+    }
 
     PointerControllerInterface& pc = *it->second;
 
@@ -264,7 +364,8 @@
     const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
     std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
     BitSet32 idBits;
-    if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) {
+    if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL &&
+        maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
         for (size_t i = 0; i < args.getPointerCount(); i++) {
             if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
                 continue;
@@ -280,7 +381,7 @@
 }
 
 void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
-    if (args.displayId == ADISPLAY_ID_NONE) {
+    if (!args.displayId.isValid()) {
         return;
     }
 
@@ -290,9 +391,12 @@
     }
 
     // Get the stylus pointer controller for the device, or create one if it doesn't exist.
-    auto [it, _] =
+    auto [it, controllerAdded] =
             mStylusPointersByDevice.try_emplace(args.deviceId,
                                                 getStylusControllerConstructor(args.displayId));
+    if (controllerAdded) {
+        onControllerAddedOrRemovedLocked();
+    }
 
     PointerControllerInterface& pc = *it->second;
 
@@ -303,7 +407,8 @@
         // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
         //   immediately by a DOWN event.
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
-        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+        pc.updatePointerIcon(mShowTouchesEnabled ? PointerIconStyle::TYPE_SPOT_HOVER
+                                                 : PointerIconStyle::TYPE_NOT_SPECIFIED);
     } else if (canUnfadeOnDisplay(args.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
@@ -332,11 +437,63 @@
     mTouchPointersByDevice.erase(args.deviceId);
     mStylusPointersByDevice.erase(args.deviceId);
     mDrawingTabletPointersByDevice.erase(args.deviceId);
+    onControllerAddedOrRemovedLocked();
+}
+
+void PointerChoreographer::onControllerAddedOrRemovedLocked() {
+    if (!com::android::input::flags::hide_pointer_indicators_for_secure_windows()) {
+        return;
+    }
+    bool requireListener = !mTouchPointersByDevice.empty() || !mMousePointersByDisplay.empty() ||
+            !mDrawingTabletPointersByDevice.empty() || !mStylusPointersByDevice.empty();
+
+    if (requireListener && mWindowInfoListener == nullptr) {
+        mWindowInfoListener = sp<PointerChoreographerDisplayInfoListener>::make(this);
+        mWindowInfoListener->setInitialDisplayInfos(mRegisterListener(mWindowInfoListener));
+        onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays());
+    } else if (!requireListener && mWindowInfoListener != nullptr) {
+        mUnregisterListener(mWindowInfoListener);
+        mWindowInfoListener = nullptr;
+    } else if (requireListener && mWindowInfoListener != nullptr) {
+        // controller may have been added to an existing privacy sensitive display, we need to
+        // update all controllers again
+        onPrivacySensitiveDisplaysChangedLocked(mWindowInfoListener->getPrivacySensitiveDisplays());
+    }
+}
+
+void PointerChoreographer::onPrivacySensitiveDisplaysChangedLocked(
+        const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays) {
+    for (auto& [_, pc] : mTouchPointersByDevice) {
+        pc->clearSkipScreenshotFlags();
+        for (auto displayId : privacySensitiveDisplays) {
+            pc->setSkipScreenshotFlagForDisplay(displayId);
+        }
+    }
+
+    for (auto& [displayId, pc] : mMousePointersByDisplay) {
+        if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) {
+            pc->setSkipScreenshotFlagForDisplay(displayId);
+        } else {
+            pc->clearSkipScreenshotFlags();
+        }
+    }
+
+    for (auto* pointerControllerByDevice :
+         {&mDrawingTabletPointersByDevice, &mStylusPointersByDevice}) {
+        for (auto& [_, pc] : *pointerControllerByDevice) {
+            auto displayId = pc->getDisplayId();
+            if (privacySensitiveDisplays.find(displayId) != privacySensitiveDisplays.end()) {
+                pc->setSkipScreenshotFlagForDisplay(displayId);
+            } else {
+                pc->clearSkipScreenshotFlags();
+            }
+        }
+    }
 }
 
 void PointerChoreographer::notifyPointerCaptureChanged(
         const NotifyPointerCaptureChangedArgs& args) {
-    if (args.request.enable) {
+    if (args.request.isEnable()) {
         std::scoped_lock _l(mLock);
         for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
             mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
@@ -345,18 +502,25 @@
     mNextListener.notify(args);
 }
 
+void PointerChoreographer::onPrivacySensitiveDisplaysChanged(
+        const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays) {
+    std::scoped_lock _l(mLock);
+    onPrivacySensitiveDisplaysChangedLocked(privacySensitiveDisplays);
+}
+
 void PointerChoreographer::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
     dump += "PointerChoreographer:\n";
-    dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
-    dump += StringPrintf("stylus pointer icon enabled: %s\n",
+    dump += StringPrintf(INDENT "Show Touches Enabled: %s\n",
+                         mShowTouchesEnabled ? "true" : "false");
+    dump += StringPrintf(INDENT "Stylus PointerIcon Enabled: %s\n",
                          mStylusPointerIconEnabled ? "true" : "false");
 
     dump += INDENT "MousePointerControllers:\n";
     for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
         std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
-        dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
+        dump += INDENT + displayId.toString() + " : " + pointerControllerDump;
     }
     dump += INDENT "TouchPointerControllers:\n";
     for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
@@ -376,7 +540,8 @@
     dump += "\n";
 }
 
-const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const {
+const DisplayViewport* PointerChoreographer::findViewportByIdLocked(
+        ui::LogicalDisplayId displayId) const {
     for (auto& viewport : mViewports) {
         if (viewport.displayId == displayId) {
             return &viewport;
@@ -385,17 +550,21 @@
     return nullptr;
 }
 
-int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const {
-    return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
+ui::LogicalDisplayId PointerChoreographer::getTargetMouseDisplayLocked(
+        ui::LogicalDisplayId associatedDisplayId) const {
+    return associatedDisplayId.isValid() ? associatedDisplayId : mDefaultMouseDisplayId;
 }
 
-std::pair<int32_t, PointerControllerInterface&> PointerChoreographer::ensureMouseControllerLocked(
-        int32_t associatedDisplayId) {
-    const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
+std::pair<ui::LogicalDisplayId, PointerControllerInterface&>
+PointerChoreographer::ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) {
+    const ui::LogicalDisplayId displayId = getTargetMouseDisplayLocked(associatedDisplayId);
 
     auto it = mMousePointersByDisplay.find(displayId);
-    LOG_ALWAYS_FATAL_IF(it == mMousePointersByDisplay.end(),
-                        "There is no mouse controller created for display %d", displayId);
+    if (it == mMousePointersByDisplay.end()) {
+        it = mMousePointersByDisplay.emplace(displayId, getMouseControllerConstructor(displayId))
+                     .first;
+        onControllerAddedOrRemovedLocked();
+    }
 
     return {displayId, *it->second};
 }
@@ -406,12 +575,12 @@
     return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
 }
 
-bool PointerChoreographer::canUnfadeOnDisplay(int32_t displayId) {
+bool PointerChoreographer::canUnfadeOnDisplay(ui::LogicalDisplayId displayId) {
     return mDisplaysWithPointersHidden.find(displayId) == mDisplaysWithPointersHidden.end();
 }
 
 PointerChoreographer::PointerDisplayChange PointerChoreographer::updatePointerControllersLocked() {
-    std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+    std::set<ui::LogicalDisplayId /*displayId*/> mouseDisplaysToKeep;
     std::set<DeviceId> touchDevicesToKeep;
     std::set<DeviceId> stylusDevicesToKeep;
     std::set<DeviceId> drawingTabletDevicesToKeep;
@@ -419,30 +588,42 @@
     // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
     // new PointerControllers if necessary.
     for (const auto& info : mInputDeviceInfos) {
+        if (!info.isEnabled()) {
+            // If device is disabled, we should not keep it, and should not show pointer for
+            // disabled mouse device.
+            continue;
+        }
         const uint32_t sources = info.getSources();
-        if (isMouseOrTouchpad(sources)) {
-            const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
+        const bool isKnownMouse = mMouseDevices.count(info.getId()) != 0;
+
+        if (isMouseOrTouchpad(sources) || isKnownMouse) {
+            const ui::LogicalDisplayId displayId =
+                    getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
             mouseDisplaysToKeep.insert(displayId);
             // For mice, show the cursor immediately when the device is first connected or
             // when it moves to a new display.
             auto [mousePointerIt, isNewMousePointer] =
                     mMousePointersByDisplay.try_emplace(displayId,
                                                         getMouseControllerConstructor(displayId));
-            auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
-            if ((isNewMouseDevice || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
+            if (isNewMousePointer) {
+                onControllerAddedOrRemovedLocked();
+            }
+
+            mMouseDevices.emplace(info.getId());
+            if ((!isKnownMouse || isNewMousePointer) && canUnfadeOnDisplay(displayId)) {
                 mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
             }
         }
         if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
-            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            info.getAssociatedDisplayId().isValid()) {
             touchDevicesToKeep.insert(info.getId());
         }
         if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
-            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            info.getAssociatedDisplayId().isValid()) {
             stylusDevicesToKeep.insert(info.getId());
         }
         if (isFromSource(sources, AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_MOUSE) &&
-            info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+            info.getAssociatedDisplayId().isValid()) {
             drawingTabletDevicesToKeep.insert(info.getId());
         }
     }
@@ -466,13 +647,15 @@
                 mInputDeviceInfos.end();
     });
 
+    onControllerAddedOrRemovedLocked();
+
     // Check if we need to notify the policy if there's a change on the pointer display ID.
     return calculatePointerDisplayChangeToNotify();
 }
 
 PointerChoreographer::PointerDisplayChange
 PointerChoreographer::calculatePointerDisplayChangeToNotify() {
-    int32_t displayIdToNotify = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayIdToNotify = ui::LogicalDisplayId::INVALID;
     FloatPoint cursorPosition = {0, 0};
     if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
         it != mMousePointersByDisplay.end()) {
@@ -490,7 +673,7 @@
     return {{displayIdToNotify, cursorPosition}};
 }
 
-void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
+void PointerChoreographer::setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) {
     PointerDisplayChange pointerDisplayChange;
 
     { // acquire lock
@@ -509,7 +692,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         for (const auto& viewport : viewports) {
-            const int32_t displayId = viewport.displayId;
+            const ui::LogicalDisplayId displayId = viewport.displayId;
             if (const auto it = mMousePointersByDisplay.find(displayId);
                 it != mMousePointersByDisplay.end()) {
                 it->second->setDisplayViewport(viewport);
@@ -535,18 +718,18 @@
 }
 
 std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
-        int32_t associatedDisplayId) {
+        ui::LogicalDisplayId associatedDisplayId) {
     std::scoped_lock _l(mLock);
-    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
+    const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
     if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
         return *viewport;
     }
     return std::nullopt;
 }
 
-FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
+FloatPoint PointerChoreographer::getMouseCursorPosition(ui::LogicalDisplayId displayId) {
     std::scoped_lock _l(mLock);
-    const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
+    const ui::LogicalDisplayId resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
     if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
         it != mMousePointersByDisplay.end()) {
         return it->second->getPosition();
@@ -585,8 +768,8 @@
 }
 
 bool PointerChoreographer::setPointerIcon(
-        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
-        DeviceId deviceId) {
+        std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+        ui::LogicalDisplayId displayId, DeviceId deviceId) {
     std::scoped_lock _l(mLock);
     if (deviceId < 0) {
         LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
@@ -610,6 +793,13 @@
     if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
         auto it = mStylusPointersByDevice.find(deviceId);
         if (it != mStylusPointersByDevice.end()) {
+            if (mShowTouchesEnabled) {
+                // If an app doesn't override the icon for the hovering stylus, show the hover icon.
+                auto* style = std::get_if<PointerIconStyle>(&icon);
+                if (style != nullptr && *style == PointerIconStyle::TYPE_NOT_SPECIFIED) {
+                    *style = PointerIconStyle::TYPE_SPOT_HOVER;
+                }
+            }
             setIconForController(icon, *it->second);
             return true;
         }
@@ -630,7 +820,7 @@
     return false;
 }
 
-void PointerChoreographer::setPointerIconVisibility(int32_t displayId, bool visible) {
+void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) {
     std::scoped_lock lock(mLock);
     if (visible) {
         mDisplaysWithPointersHidden.erase(displayId);
@@ -652,8 +842,13 @@
     }
 }
 
+void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mCurrentFocusedDisplay = displayId;
+}
+
 PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
-        int32_t displayId) {
+        ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
             [this, displayId]() REQUIRES(mLock) {
                 auto pc = mPolicy.createPointerController(
@@ -667,7 +862,7 @@
 }
 
 PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
-        int32_t displayId) {
+        ui::LogicalDisplayId displayId) {
     std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
             [this, displayId]() REQUIRES(mLock) {
                 auto pc = mPolicy.createPointerController(
@@ -680,4 +875,36 @@
     return ConstructorDelegate(std::move(ctor));
 }
 
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::onWindowInfosChanged(
+        const gui::WindowInfosUpdate& windowInfosUpdate) {
+    std::scoped_lock _l(mListenerLock);
+    if (mPointerChoreographer == nullptr) {
+        return;
+    }
+    auto newPrivacySensitiveDisplays =
+            getPrivacySensitiveDisplaysFromWindowInfos(windowInfosUpdate.windowInfos);
+    if (newPrivacySensitiveDisplays != mPrivacySensitiveDisplays) {
+        mPrivacySensitiveDisplays = std::move(newPrivacySensitiveDisplays);
+        mPointerChoreographer->onPrivacySensitiveDisplaysChanged(mPrivacySensitiveDisplays);
+    }
+}
+
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::setInitialDisplayInfos(
+        const std::vector<gui::WindowInfo>& windowInfos) {
+    std::scoped_lock _l(mListenerLock);
+    mPrivacySensitiveDisplays = getPrivacySensitiveDisplaysFromWindowInfos(windowInfos);
+}
+
+std::unordered_set<ui::LogicalDisplayId /*displayId*/>
+PointerChoreographer::PointerChoreographerDisplayInfoListener::getPrivacySensitiveDisplays() {
+    std::scoped_lock _l(mListenerLock);
+    return mPrivacySensitiveDisplays;
+}
+
+void PointerChoreographer::PointerChoreographerDisplayInfoListener::
+        onPointerChoreographerDestroyed() {
+    std::scoped_lock _l(mListenerLock);
+    mPointerChoreographer = nullptr;
+}
+
 } // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index a3c210e..635487b 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -21,7 +21,9 @@
 #include "PointerChoreographerPolicyInterface.h"
 
 #include <android-base/thread_annotations.h>
+#include <gui/WindowInfosListener.h>
 #include <type_traits>
+#include <unordered_set>
 
 namespace android {
 
@@ -53,11 +55,11 @@
      * Set the display that pointers, like the mouse cursor and drawing tablets,
      * should be drawn on.
      */
-    virtual void setDefaultMouseDisplayId(int32_t displayId) = 0;
+    virtual void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) = 0;
     virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
     virtual std::optional<DisplayViewport> getViewportForPointerDevice(
-            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
-    virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
+            ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
+    virtual FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) = 0;
     virtual void setShowTouchesEnabled(bool enabled) = 0;
     virtual void setStylusPointerIconEnabled(bool enabled) = 0;
     /**
@@ -66,12 +68,17 @@
      * Returns true if the icon was changed successfully, false otherwise.
      */
     virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                                int32_t displayId, DeviceId deviceId) = 0;
+                                ui::LogicalDisplayId displayId, DeviceId deviceId) = 0;
     /**
      * Set whether pointer icons for mice, touchpads, and styluses should be visible on the
      * given display.
      */
-    virtual void setPointerIconVisibility(int32_t displayId, bool visible) = 0;
+    virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0;
+
+    /**
+     * Used by Dispatcher to notify changes in the current focused display.
+     */
+    virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
 
     /**
      * This method may be called on any thread (usually by the input manager on a binder thread).
@@ -83,21 +90,21 @@
 public:
     explicit PointerChoreographer(InputListenerInterface& listener,
                                   PointerChoreographerPolicyInterface&);
-    ~PointerChoreographer() override = default;
+    ~PointerChoreographer() override;
 
-    void setDefaultMouseDisplayId(int32_t displayId) override;
+    void setDefaultMouseDisplayId(ui::LogicalDisplayId displayId) override;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
     std::optional<DisplayViewport> getViewportForPointerDevice(
-            int32_t associatedDisplayId) override;
-    FloatPoint getMouseCursorPosition(int32_t displayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
+    FloatPoint getMouseCursorPosition(ui::LogicalDisplayId displayId) override;
     void setShowTouchesEnabled(bool enabled) override;
     void setStylusPointerIconEnabled(bool enabled) override;
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
-                        int32_t displayId, DeviceId deviceId) override;
-    void setPointerIconVisibility(int32_t displayId, bool visible) override;
+                        ui::LogicalDisplayId displayId, DeviceId deviceId) override;
+    void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
+    void setFocusedDisplay(ui::LogicalDisplayId displayId) 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;
@@ -109,17 +116,20 @@
     void dump(std::string& dump) override;
 
 private:
-    using PointerDisplayChange =
-            std::optional<std::tuple<int32_t /*displayId*/, FloatPoint /*cursorPosition*/>>;
+    using PointerDisplayChange = std::optional<
+            std::tuple<ui::LogicalDisplayId /*displayId*/, FloatPoint /*cursorPosition*/>>;
     [[nodiscard]] PointerDisplayChange updatePointerControllersLocked() REQUIRES(mLock);
     [[nodiscard]] PointerDisplayChange calculatePointerDisplayChangeToNotify() REQUIRES(mLock);
-    const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
-    int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
-    std::pair<int32_t /*displayId*/, PointerControllerInterface&> ensureMouseControllerLocked(
-            int32_t associatedDisplayId) REQUIRES(mLock);
+    const DisplayViewport* findViewportByIdLocked(ui::LogicalDisplayId displayId) const
+            REQUIRES(mLock);
+    ui::LogicalDisplayId getTargetMouseDisplayLocked(ui::LogicalDisplayId associatedDisplayId) const
+            REQUIRES(mLock);
+    std::pair<ui::LogicalDisplayId /*displayId*/, PointerControllerInterface&>
+    ensureMouseControllerLocked(ui::LogicalDisplayId associatedDisplayId) REQUIRES(mLock);
     InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
-    bool canUnfadeOnDisplay(int32_t displayId) REQUIRES(mLock);
+    bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock);
 
+    void fadeMouseCursorOnKeyPress(const NotifyKeyArgs& args);
     NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
     NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
@@ -127,20 +137,52 @@
     void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
     void processDeviceReset(const NotifyDeviceResetArgs& args);
+    void onControllerAddedOrRemovedLocked() REQUIRES(mLock);
+    void onPrivacySensitiveDisplaysChangedLocked(
+            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays)
+            REQUIRES(mLock);
+    void onPrivacySensitiveDisplaysChanged(
+            const std::unordered_set<ui::LogicalDisplayId>& privacySensitiveDisplays);
+
+    /* This listener keeps tracks of visible privacy sensitive displays and updates the
+     * choreographer if there are any changes.
+     *
+     * Listener uses mListenerLock to guard all private data as choreographer and SurfaceComposer
+     * both can call into the listener. To prevent deadlocks Choreographer can call listener with
+     * its lock held, but listener must not call choreographer with its lock.
+     */
+    class PointerChoreographerDisplayInfoListener : public gui::WindowInfosListener {
+    public:
+        explicit PointerChoreographerDisplayInfoListener(PointerChoreographer* pc)
+              : mPointerChoreographer(pc){};
+        void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
+        void setInitialDisplayInfos(const std::vector<gui::WindowInfo>& windowInfos);
+        std::unordered_set<ui::LogicalDisplayId /*displayId*/> getPrivacySensitiveDisplays();
+        void onPointerChoreographerDestroyed();
+
+    private:
+        std::mutex mListenerLock;
+        PointerChoreographer* mPointerChoreographer GUARDED_BY(mListenerLock);
+        std::unordered_set<ui::LogicalDisplayId /*displayId*/> mPrivacySensitiveDisplays
+                GUARDED_BY(mListenerLock);
+    };
+    sp<PointerChoreographerDisplayInfoListener> mWindowInfoListener GUARDED_BY(mLock);
 
     using ControllerConstructor =
             ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
     ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
-    ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
-    ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
+    ControllerConstructor getMouseControllerConstructor(ui::LogicalDisplayId displayId)
+            REQUIRES(mLock);
+    ControllerConstructor getStylusControllerConstructor(ui::LogicalDisplayId displayId)
+            REQUIRES(mLock);
 
     std::mutex mLock;
 
     InputListenerInterface& mNextListener;
     PointerChoreographerPolicyInterface& mPolicy;
 
-    std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
-            GUARDED_BY(mLock);
+    std::map<ui::LogicalDisplayId, std::shared_ptr<PointerControllerInterface>>
+            mMousePointersByDisplay GUARDED_BY(mLock);
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
             GUARDED_BY(mLock);
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
@@ -148,14 +190,29 @@
     std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mDrawingTabletPointersByDevice
             GUARDED_BY(mLock);
 
-    int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
-    int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mDefaultMouseDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mNotifiedPointerDisplayId GUARDED_BY(mLock);
     std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
     std::set<DeviceId> mMouseDevices GUARDED_BY(mLock);
     std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
     bool mShowTouchesEnabled GUARDED_BY(mLock);
     bool mStylusPointerIconEnabled GUARDED_BY(mLock);
-    std::set<int32_t /*displayId*/> mDisplaysWithPointersHidden;
+    std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
+    ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(mLock);
+
+protected:
+    using WindowListenerRegisterConsumer = std::function<std::vector<gui::WindowInfo>(
+            const sp<android::gui::WindowInfosListener>&)>;
+    using WindowListenerUnregisterConsumer =
+            std::function<void(const sp<android::gui::WindowInfosListener>&)>;
+    explicit PointerChoreographer(InputListenerInterface& listener,
+                                  PointerChoreographerPolicyInterface&,
+                                  const WindowListenerRegisterConsumer& registerListener,
+                                  const WindowListenerUnregisterConsumer& unregisterListener);
+
+private:
+    const WindowListenerRegisterConsumer mRegisterListener;
+    const WindowListenerUnregisterConsumer mUnregisterListener;
 };
 
 } // namespace android
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 293ad66..43eaf67 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
 {
   "presubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
+      "name": "CtsWindowManagerDeviceInput",
       "options": [
         {
-          "include-filter": "android.server.wm.window.WindowInputTests"
+          "include-filter": "android.server.wm.input.WindowInputTests"
         }
       ]
     },
@@ -35,109 +35,34 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.hardware.input"
-        }
-      ]
+      "name": "FrameworksCoreTests_hardware_input"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_android_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
@@ -146,16 +71,17 @@
           "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
         }
       ]
+    },
+    {
+      "name": "monkey_test"
+    },
+    {
+      "name": "CtsSurfaceControlTests"
     }
   ],
   "postsubmit": [
     {
-      "name": "CtsWindowManagerDeviceWindow",
-      "options": [
-        {
-          "include-filter": "android.server.wm.window.WindowInputTests"
-        }
-      ]
+      "name": "CtsWindowManagerDeviceWindow_window_windowinputtests"
     },
     {
       "name": "libinput_tests"
@@ -181,98 +107,31 @@
       ]
     },
     {
-      "name": "CtsHardwareTestCases",
-      "options": [
-        {
-          "include-filter": "android.hardware.input.cts.tests"
-        }
-      ]
+      "name": "CtsHardwareTestCases_cts_tests"
     },
     {
       "name": "CtsInputTestCases"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.input"
-        }
-      ]
+      "name": "CtsViewTestCases_cts_input"
     },
     {
-      "name": "CtsViewTestCases",
-      "options": [
-        {
-          "include-filter": "android.view.cts.HoverTest"
-        },
-        {
-          "include-filter": "android.view.cts.MotionEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.PointerCaptureTest"
-        },
-        {
-          "include-filter": "android.view.cts.TooltipTest"
-        },
-        {
-          "include-filter": "android.view.cts.TouchDelegateTest"
-        },
-        {
-          "include-filter": "android.view.cts.VerifyInputEventTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewTest"
-        },
-        {
-          "include-filter": "android.view.cts.ViewUnbufferedTest"
-        }
-      ]
+      "name": "CtsViewTestCases_input_related"
     },
     {
-      "name": "CtsWidgetTestCases",
-      "options": [
-        {
-          "include-filter": "android.widget.cts.NumberPickerTest"
-        },
-        {
-          "include-filter": "android.widget.cts.SeekBarTest"
-        }
-      ]
+      "name": "CtsWidgetTestCases_seekbar_and_numberpicker"
     },
     {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.view.VerifiedKeyEventTest"
-        },
-        {
-          "include-filter": "android.view.VerifiedMotionEventTest"
-        }
-      ]
+      "name": "FrameworksCoreTests_view_verified"
     },
     {
-      "name": "CtsAppTestCases",
-      "options": [
-        {
-          "include-filter": "android.app.cts.ToolbarActionBarTest"
-        }
-      ]
+      "name": "CtsAppTestCases_cts_toolbaractionbartest"
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.input"
-        }
-      ]
+      "name": "FrameworksServicesTests_server_input"
     },
     {
-      "name": "CtsSecurityTestCases",
-      "options": [
-        {
-          "include-filter": "android.security.cts.MotionEventTest"
-        }
-      ]
+      "name": "CtsSecurityTestCases_cts_motioneventtest"
     },
     {
       "name": "CtsSecurityBulletinHostTestCases",
@@ -284,11 +143,22 @@
     },
     {
       "name": "CtsInputHostTestCases"
+    },
+    {
+      "name": "monkey_test"
+    },
+    {
+      "name": "CtsInputRootTestCases"
+    }
+  ],
+  "platinum-postsubmit": [
+    {
+      "name": "inputflinger_tests"
     }
   ],
   "staged-platinum-postsubmit": [
     {
-      "name": "inputflinger_tests"
+      "name": "libinput_tests"
     }
   ]
 }
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index 1e2b9b3a..0e9ec91 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -342,12 +342,6 @@
                                                        bool enablePalmRejection)
       : mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
 
-void UnwantedInteractionBlocker::notifyConfigurationChanged(
-        const NotifyConfigurationChangedArgs& args) {
-    mQueuedListener.notifyConfigurationChanged(args);
-    mQueuedListener.flush();
-}
-
 void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
     mQueuedListener.notifyKey(args);
     mQueuedListener.flush();
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
index 419da83..8a66e25 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.h
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -91,7 +91,6 @@
     explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection);
 
     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;
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
index b9e6a03..5b0b9ac 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
@@ -23,4 +23,5 @@
 parcelable DeviceInfo {
     int deviceId;
     boolean external;
+    int keyboardType;
 }
\ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
index 994d1c4..31b7231 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -54,5 +54,7 @@
 
     /** Notifies when configuration changes */
     void notifyConfigurationChanged(in InputFilterConfiguration config);
+
+    String dumpFilter();
 }
 
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
index 2f6b8fc..cc0592e 100644
--- a/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputThread.aidl
@@ -21,6 +21,13 @@
   * infrastructure.
   *
   * <p>
+  * Earlier, we used rust thread park()/unpark() to put the thread to sleep and wake up from sleep.
+  * But that caused some breakages after migrating the rust system crates to 2021 edition. Since,
+  * the threads are created in C++, it was more reliable to rely on C++ side of the implementation
+  * to implement the sleep and wake functions.
+  * </p>
+  *
+  * <p>
   * NOTE: Tried using rust provided threading infrastructure but that uses std::thread which doesn't
   * have JNI support and can't call into Java policy that we use currently. libutils provided
   * Thread.h also recommends against using std::thread and using the provided infrastructure that
@@ -33,6 +40,16 @@
     /** Finish input thread (if not running, this call does nothing) */
     void finish();
 
+    /** Wakes up the thread (if sleeping) */
+    void wake();
+
+    /**
+      * Puts the thread to sleep until a future time provided.
+      *
+      * NOTE: The thread can be awaken before the provided time using {@link wake()} function.
+      */
+    void sleepUntil(long whenNanos);
+
     /** Callbacks from C++ to call into inputflinger rust components */
     interface IInputThreadCallback {
         /**
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 2d12574..4385072 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -11,6 +11,7 @@
 cc_benchmark {
     name: "inputflinger_benchmarks",
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "InputDispatcher_benchmarks.cpp",
     ],
     defaults: [
@@ -31,6 +32,8 @@
     ],
     static_libs: [
         "libattestation",
+        "libgmock",
+        "libgtest",
         "libinputdispatcher",
     ],
 }
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 5ae3715..96c8640 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -18,11 +18,10 @@
 
 #include <android/os/IInputConstants.h>
 #include <binder/Binder.h>
-#include <gui/constants.h>
 #include "../dispatcher/InputDispatcher.h"
 #include "../tests/FakeApplicationHandle.h"
 #include "../tests/FakeInputDispatcherPolicy.h"
-#include "../tests/FakeWindowHandle.h"
+#include "../tests/FakeWindows.h"
 
 using android::base::Result;
 using android::gui::WindowInfo;
@@ -38,7 +37,7 @@
 constexpr DeviceId DEVICE_ID = 1;
 
 // An arbitrary display id
-constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
 
@@ -63,7 +62,7 @@
     ui::Transform identityTransform;
     MotionEvent event;
     event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
-                     ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
+                     ui::LogicalDisplayId::DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
                      /* actionButton */ 0, /* flags */ 0,
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
@@ -89,7 +88,7 @@
     const nsecs_t currentTime = now();
     // Define a valid motion event.
     NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime,
-                          DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                          DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                           POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN,
                           /* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
                           MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
@@ -104,16 +103,16 @@
 static void benchmarkNotifyMotion(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
-    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs = generateMotionArgs();
 
@@ -122,60 +121,60 @@
         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->consumeMotion();
-        window->consumeMotion();
+        window->consumeMotionEvent();
+        window->consumeMotionEvent();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 static void benchmarkInjectMotion(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window that will receive motion events
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> window =
             sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
 
-    dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    dispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     for (auto _ : state) {
         MotionEvent event = generateMotionEvent();
         // Send ACTION_DOWN
-        dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
-                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
+                                     INJECT_EVENT_TIMEOUT,
+                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
         // Send ACTION_UP
         event.setAction(AMOTION_EVENT_ACTION_UP);
-        dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
-                                    INJECT_EVENT_TIMEOUT,
-                                    POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
+        dispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
+                                     INJECT_EVENT_TIMEOUT,
+                                     POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
 
-        window->consumeMotion();
-        window->consumeMotion();
+        window->consumeMotionEvent();
+        window->consumeMotionEvent();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
     // Create dispatcher
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
-    dispatcher.start();
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+    dispatcher->start();
 
     // Create a window
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -188,12 +187,12 @@
     std::vector<gui::DisplayInfo> displayInfos{info};
 
     for (auto _ : state) {
-        dispatcher.onWindowInfosChanged(
+        dispatcher->onWindowInfosChanged(
                 {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0});
-        dispatcher.onWindowInfosChanged(
+        dispatcher->onWindowInfosChanged(
                 {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0});
     }
-    dispatcher.stop();
+    dispatcher->stop();
 }
 
 } // namespace
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 70c3ad1..1a0ec48 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -61,6 +61,8 @@
     ],
     shared_libs: [
         "libbase",
+        "libbinder",
+        "libbinder_ndk",
         "libcrypto",
         "libcutils",
         "libinput",
@@ -71,6 +73,7 @@
         "libutils",
         "libstatspull",
         "libstatssocket",
+        "packagemanager_aidl-cpp",
         "server_configurable_flags",
     ],
     static_libs: [
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 83e6a60..568d348 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include "trace/EventTrackerInterface.h"
+
 #include <input/Input.h>
 #include <bitset>
 #include <optional>
@@ -30,7 +32,8 @@
         CANCEL_POINTER_EVENTS = 1,
         CANCEL_NON_POINTER_EVENTS = 2,
         CANCEL_FALLBACK_EVENTS = 3,
-        ftl_last = CANCEL_FALLBACK_EVENTS,
+        CANCEL_HOVER_EVENTS = 4,
+        ftl_last = CANCEL_HOVER_EVENTS
     };
 
     // The criterion to use to determine which events should be canceled.
@@ -46,12 +49,18 @@
     std::optional<int32_t> deviceId = std::nullopt;
 
     // The specific display id of events to cancel, or nullopt to cancel events on any display.
-    std::optional<int32_t> displayId = std::nullopt;
+    std::optional<ui::LogicalDisplayId> displayId = std::nullopt;
 
     // The specific pointers to cancel, or nullopt to cancel all pointer events
     std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
 
-    CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
+    const std::unique_ptr<trace::EventTrackerInterface>& traceTracker;
+
+    explicit CancelationOptions(Mode mode, const char* reason,
+                                const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+          : mode(mode), reason(reason), traceTracker(traceTracker) {}
+    CancelationOptions(const CancelationOptions&) = delete;
+    CancelationOptions operator=(const CancelationOptions&) = delete;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/DragState.h b/services/inputflinger/dispatcher/DragState.h
index 9809148..1ed6c29 100644
--- a/services/inputflinger/dispatcher/DragState.h
+++ b/services/inputflinger/dispatcher/DragState.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <gui/WindowInfo.h>
+#include <input/Input.h>
 #include <utils/StrongPointer.h>
 #include <string>
 
@@ -25,8 +26,9 @@
 namespace inputdispatcher {
 
 struct DragState {
-    DragState(const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t pointerId)
-          : dragWindow(windowHandle), pointerId(pointerId) {}
+    DragState(const sp<android::gui::WindowInfoHandle>& windowHandle, DeviceId deviceId,
+              int32_t pointerId)
+          : dragWindow(windowHandle), deviceId(deviceId), pointerId(pointerId) {}
     void dump(std::string& dump, const char* prefix = "");
 
     // The window being dragged.
@@ -37,6 +39,8 @@
     bool isStartDrag = false;
     // Indicate if the stylus button is down at the start of the drag.
     bool isStylusButtonDownAtStart = false;
+    // Indicate which device started this drag and drop.
+    const DeviceId deviceId;
     // Indicate which pointer id is tracked by the drag and drop.
     const int32_t pointerId;
 };
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 264dc03..ff407af 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -23,6 +23,7 @@
 
 #include <android-base/stringprintf.h>
 #include <cutils/atomic.h>
+#include <ftl/enum.h>
 #include <inttypes.h>
 
 using android::base::StringPrintf;
@@ -67,15 +68,6 @@
         injectionState(nullptr),
         dispatchInProgress(false) {}
 
-// --- ConfigurationChangedEntry ---
-
-ConfigurationChangedEntry::ConfigurationChangedEntry(int32_t id, nsecs_t eventTime)
-      : EventEntry(id, Type::CONFIGURATION_CHANGED, eventTime, 0) {}
-
-std::string ConfigurationChangedEntry::getDescription() const {
-    return StringPrintf("ConfigurationChangedEvent(), policyFlags=0x%08x", policyFlags);
-}
-
 // --- DeviceResetEntry ---
 
 DeviceResetEntry::DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId)
@@ -110,7 +102,7 @@
 
 std::string PointerCaptureChangedEntry::getDescription() const {
     return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
-                        pointerCaptureRequest.enable ? "true" : "false");
+                        pointerCaptureRequest.isEnable() ? "true" : "false");
 }
 
 // --- DragEntry ---
@@ -131,9 +123,9 @@
 // --- KeyEntry ---
 
 KeyEntry::KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-                   int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-                   int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
-                   int32_t metaState, int32_t repeatCount, nsecs_t downTime)
+                   int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+                   uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode,
+                   int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime)
       : EventEntry(id, Type::KEY, eventTime, policyFlags),
         deviceId(deviceId),
         source(source),
@@ -155,13 +147,14 @@
     if (!IS_DEBUGGABLE_BUILD) {
         return "KeyEvent";
     }
-    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
-                        ", action=%s, "
+    return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%s, "
+                        "action=%s, "
                         "flags=0x%08x, keyCode=%s(%d), scanCode=%d, metaState=0x%08x, "
                         "repeatCount=%d), policyFlags=0x%08x",
-                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
-                        KeyEvent::actionToString(action), flags, KeyEvent::getLabel(keyCode),
-                        keyCode, scanCode, metaState, repeatCount, policyFlags);
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(),
+                        displayId.toString().c_str(), KeyEvent::actionToString(action), flags,
+                        KeyEvent::getLabel(keyCode), keyCode, scanCode, metaState, repeatCount,
+                        policyFlags);
 }
 
 std::ostream& operator<<(std::ostream& out, const KeyEntry& keyEntry) {
@@ -171,7 +164,8 @@
 
 // --- TouchModeEntry ---
 
-TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId)
+TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode,
+                               ui::LogicalDisplayId displayId)
       : EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
         inTouchMode(inTouchMode),
         displayId(displayId) {}
@@ -183,12 +177,13 @@
 // --- MotionEntry ---
 
 MotionEntry::MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState,
-                         nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
-                         uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
-                         int32_t metaState, int32_t buttonState,
-                         MotionClassification classification, int32_t edgeFlags, float xPrecision,
-                         float yPrecision, float xCursorPosition, float yCursorPosition,
-                         nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+                         nsecs_t eventTime, int32_t deviceId, uint32_t source,
+                         ui::LogicalDisplayId displayId, uint32_t policyFlags, int32_t action,
+                         int32_t actionButton, int32_t flags, int32_t metaState,
+                         int32_t buttonState, MotionClassification classification,
+                         int32_t edgeFlags, float xPrecision, float yPrecision,
+                         float xCursorPosition, float yCursorPosition, nsecs_t downTime,
+                         const std::vector<PointerProperties>& pointerProperties,
                          const std::vector<PointerCoords>& pointerCoords)
       : EventEntry(id, Type::MOTION, eventTime, policyFlags),
         deviceId(deviceId),
@@ -217,15 +212,16 @@
     }
     std::string msg;
     msg += StringPrintf("MotionEvent(deviceId=%d, eventTime=%" PRIu64
-                        ", source=%s, displayId=%" PRId32
-                        ", action=%s, actionButton=0x%08x, flags=0x%08x, metaState=0x%08x, "
+                        ", source=%s, displayId=%s, action=%s, actionButton=0x%08x, flags=0x%08x,"
+                        " metaState=0x%08x, "
                         "buttonState=0x%08x, "
                         "classification=%s, edgeFlags=0x%08x, xPrecision=%.1f, yPrecision=%.1f, "
                         "xCursorPosition=%0.1f, yCursorPosition=%0.1f, pointers=[",
-                        deviceId, eventTime, inputEventSourceToString(source).c_str(), displayId,
-                        MotionEvent::actionToString(action).c_str(), actionButton, flags, metaState,
-                        buttonState, motionClassificationToString(classification), edgeFlags,
-                        xPrecision, yPrecision, xCursorPosition, yCursorPosition);
+                        deviceId, eventTime, inputEventSourceToString(source).c_str(),
+                        displayId.toString().c_str(), MotionEvent::actionToString(action).c_str(),
+                        actionButton, flags, metaState, buttonState,
+                        motionClassificationToString(classification), edgeFlags, xPrecision,
+                        yPrecision, xCursorPosition, yCursorPosition);
 
     for (uint32_t i = 0; i < getPointerCount(); i++) {
         if (i) {
@@ -261,9 +257,10 @@
 std::string SensorEntry::getDescription() const {
     std::string msg;
     msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
-                        "accuracy=0x%08x, hwTimestamp=%" PRId64,
+                        "accuracy=%s, hwTimestamp=%" PRId64,
                         deviceId, inputEventSourceToString(source).c_str(),
-                        ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
+                        ftl::enum_string(sensorType).c_str(), ftl::enum_string(accuracy).c_str(),
+                        hwTimestamp);
 
     if (IS_DEBUGGABLE_BUILD) {
         for (size_t i = 0; i < values.size(); i++) {
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 1298b5d..becfb05 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -32,7 +32,6 @@
 
 struct EventEntry {
     enum class Type {
-        CONFIGURATION_CHANGED,
         DEVICE_RESET,
         FOCUS,
         KEY,
@@ -78,11 +77,6 @@
     virtual ~EventEntry() = default;
 };
 
-struct ConfigurationChangedEntry : EventEntry {
-    explicit ConfigurationChangedEntry(int32_t id, nsecs_t eventTime);
-    std::string getDescription() const override;
-};
-
 struct DeviceResetEntry : EventEntry {
     int32_t deviceId;
 
@@ -120,7 +114,7 @@
 struct KeyEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t keyCode;
     int32_t scanCode;
@@ -140,12 +134,13 @@
     mutable InterceptKeyResult interceptKeyResult; // set based on the interception result
     mutable nsecs_t interceptKeyWakeupTime;        // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
     mutable int32_t flags;
+    // TODO(b/328618922): Refactor key repeat generation to make repeatCount non-mutable.
     mutable int32_t repeatCount;
 
     KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-             int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-             int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-             int32_t repeatCount, nsecs_t downTime);
+             int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+             uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+             int32_t metaState, int32_t repeatCount, nsecs_t downTime);
     std::string getDescription() const override;
 };
 
@@ -154,7 +149,7 @@
 struct MotionEntry : EventEntry {
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t actionButton;
     int32_t flags;
@@ -174,11 +169,12 @@
     size_t getPointerCount() const { return pointerProperties.size(); }
 
     MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
-                int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
-                int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
-                int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
-                float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
-                nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+                int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId,
+                uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+                int32_t metaState, int32_t buttonState, MotionClassification classification,
+                int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
+                float yCursorPosition, nsecs_t downTime,
+                const std::vector<PointerProperties>& pointerProperties,
                 const std::vector<PointerCoords>& pointerCoords);
     std::string getDescription() const override;
 };
@@ -204,9 +200,9 @@
 
 struct TouchModeEntry : EventEntry {
     bool inTouchMode;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
 
-    TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId);
+    TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, ui::LogicalDisplayId displayId);
     std::string getDescription() const override;
 };
 
diff --git a/services/inputflinger/dispatcher/EventLogTags.logtags b/services/inputflinger/dispatcher/EventLogTags.logtags
index 2c5fe21..4dd6073 100644
--- a/services/inputflinger/dispatcher/EventLogTags.logtags
+++ b/services/inputflinger/dispatcher/EventLogTags.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 62000 - 62199 reserved for inputflinger
 
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index 0e4e79e..b374fad 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -41,12 +41,12 @@
     size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
 };
 
-sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const {
+sp<IBinder> FocusResolver::getFocusedWindowToken(ui::LogicalDisplayId displayId) const {
     auto it = mFocusedWindowTokenByDisplay.find(displayId);
     return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
 }
 
-std::optional<FocusRequest> FocusResolver::getFocusRequest(int32_t displayId) {
+std::optional<FocusRequest> FocusResolver::getFocusRequest(ui::LogicalDisplayId displayId) {
     auto it = mFocusRequestByDisplay.find(displayId);
     return it != mFocusRequestByDisplay.end() ? std::make_optional<>(it->second) : std::nullopt;
 }
@@ -58,7 +58,7 @@
  * we will check if the previous focus request is eligible to receive focus.
  */
 std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(
-        int32_t displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
+        ui::LogicalDisplayId displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
     std::string removeFocusReason;
 
     const std::optional<FocusRequest> request = getFocusRequest(displayId);
@@ -94,12 +94,11 @@
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
         const FocusRequest& request, const std::vector<sp<WindowInfoHandle>>& windows) {
-    const int32_t displayId = request.displayId;
+    const ui::LogicalDisplayId displayId = ui::LogicalDisplayId{request.displayId};
     const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
     if (currentFocus == request.token) {
-        ALOGD_IF(DEBUG_FOCUS,
-                 "setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
-                 request.windowName.c_str(), displayId);
+        ALOGD_IF(DEBUG_FOCUS, "setFocusedWindow %s on display %s ignored, reason: already focused",
+                 request.windowName.c_str(), displayId.toString().c_str());
         return std::nullopt;
     }
 
@@ -193,7 +192,7 @@
 }
 
 std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
-        int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,
+        ui::LogicalDisplayId displayId, const std::string& reason, const sp<IBinder>& newFocus,
         const std::string& tokenName) {
     sp<IBinder> oldFocus = getFocusedWindowToken(displayId);
     if (newFocus == oldFocus) {
@@ -216,8 +215,8 @@
     std::string dump;
     dump += INDENT "FocusedWindows:\n";
     for (const auto& [displayId, namedToken] : mFocusedWindowTokenByDisplay) {
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s'\n", displayId,
-                                   namedToken.first.c_str());
+        dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s'\n",
+                                   displayId.toString().c_str(), namedToken.first.c_str());
     }
     return dump;
 }
@@ -233,13 +232,14 @@
         auto it = mLastFocusResultByDisplay.find(displayId);
         std::string result =
                 it != mLastFocusResultByDisplay.end() ? ftl::enum_string(it->second) : "";
-        dump += base::StringPrintf(INDENT2 "displayId=%" PRId32 ", name='%s' result='%s'\n",
-                                   displayId, request.windowName.c_str(), result.c_str());
+        dump += base::StringPrintf(INDENT2 "displayId=%s, name='%s' result='%s'\n",
+                                   displayId.toString().c_str(), request.windowName.c_str(),
+                                   result.c_str());
     }
     return dump;
 }
 
-void FocusResolver::displayRemoved(int32_t displayId) {
+void FocusResolver::displayRemoved(ui::LogicalDisplayId displayId) {
     mFocusRequestByDisplay.erase(displayId);
     mLastFocusResultByDisplay.erase(displayId);
 }
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index 5bb157b..2910ba4 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -49,22 +49,23 @@
 class FocusResolver {
 public:
     // Returns the focused window token on the specified display.
-    sp<IBinder> getFocusedWindowToken(int32_t displayId) const;
+    sp<IBinder> getFocusedWindowToken(ui::LogicalDisplayId displayId) const;
 
     struct FocusChanges {
         sp<IBinder> oldFocus;
         sp<IBinder> newFocus;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId;
         std::string reason;
     };
     std::optional<FocusResolver::FocusChanges> setInputWindows(
-            int32_t displayId, const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
+            ui::LogicalDisplayId displayId,
+            const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
     std::optional<FocusResolver::FocusChanges> setFocusedWindow(
             const android::gui::FocusRequest& request,
             const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
 
     // Display has been removed from the system, clean up old references.
-    void displayRemoved(int32_t displayId);
+    void displayRemoved(ui::LogicalDisplayId displayId);
 
     // exposed for debugging
     bool hasFocusedWindowTokens() const { return !mFocusedWindowTokenByDisplay.empty(); }
@@ -105,20 +106,23 @@
     // the same token. Focus is tracked by the token per display and the events are dispatched
     // to the channel associated by this token.
     typedef std::pair<std::string /* name */, sp<IBinder>> NamedToken;
-    std::unordered_map<int32_t /* displayId */, NamedToken> mFocusedWindowTokenByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, NamedToken>
+            mFocusedWindowTokenByDisplay;
 
     // This map will store the focus request per display. When the input window handles are updated,
     // the current request will be checked to see if it can be processed at that time.
-    std::unordered_map<int32_t /* displayId */, android::gui::FocusRequest> mFocusRequestByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, android::gui::FocusRequest>
+            mFocusRequestByDisplay;
 
     // Last reason for not granting a focus request. This is used to add more debug information
     // in the event logs.
-    std::unordered_map<int32_t /* displayId */, Focusability> mLastFocusResultByDisplay;
+    std::unordered_map<ui::LogicalDisplayId /* displayId */, Focusability>
+            mLastFocusResultByDisplay;
 
     std::optional<FocusResolver::FocusChanges> updateFocusedWindow(
-            int32_t displayId, const std::string& reason, const sp<IBinder>& token,
+            ui::LogicalDisplayId displayId, const std::string& reason, const sp<IBinder>& token,
             const std::string& tokenName = "");
-    std::optional<android::gui::FocusRequest> getFocusRequest(int32_t displayId);
+    std::optional<android::gui::FocusRequest> getFocusRequest(ui::LogicalDisplayId displayId);
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 2fd1763..56ab0dc 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -52,6 +52,7 @@
 #include "Connection.h"
 #include "DebugConfig.h"
 #include "InputDispatcher.h"
+#include "InputEventTimeline.h"
 #include "trace/InputTracer.h"
 #include "trace/InputTracingPerfettoBackend.h"
 #include "trace/ThreadedBackend.h"
@@ -103,6 +104,26 @@
     }
 }
 
+// Helper to get a trace tracker from a traced key or motion entry.
+const std::unique_ptr<trace::EventTrackerInterface>& getTraceTracker(const EventEntry& entry) {
+    switch (entry.type) {
+        case EventEntry::Type::MOTION: {
+            const auto& motion = static_cast<const MotionEntry&>(entry);
+            ensureEventTraced(motion);
+            return motion.traceTracker;
+        }
+        case EventEntry::Type::KEY: {
+            const auto& key = static_cast<const KeyEntry&>(entry);
+            ensureEventTraced(key);
+            return key.traceTracker;
+        }
+        default: {
+            const static std::unique_ptr<trace::EventTrackerInterface> kNullTracker;
+            return kNullTracker;
+        }
+    }
+}
+
 // Temporarily releases a held mutex for the lifetime of the instance.
 // Named to match std::scoped_lock
 class scoped_unlock {
@@ -379,7 +400,8 @@
                                                    const InputTarget& inputTarget,
                                                    std::shared_ptr<const EventEntry> eventEntry,
                                                    ftl::Flags<InputTarget::Flags> inputTargetFlags,
-                                                   int64_t vsyncId) {
+                                                   int64_t vsyncId,
+                                                   trace::InputTracerInterface* tracer) {
     const bool zeroCoords = inputTargetFlags.test(InputTarget::Flags::ZERO_COORDS);
     const sp<WindowInfoHandle> win = inputTarget.windowHandle;
     const std::optional<int32_t> windowId =
@@ -423,10 +445,12 @@
             newCoords.copyFrom(motionEntry.pointerCoords[i]);
             // First, apply the current pointer's transform to update the coordinates into
             // window space.
-            newCoords.transform(currTransform);
+            MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source,
+                                                           motionEntry.flags, currTransform);
             // Next, apply the inverse transform of the normalized coordinates so the
             // current coordinates are transformed into the normalized coordinate space.
-            newCoords.transform(inverseTransform);
+            MotionEvent::calculateTransformedCoordsInPlace(newCoords, motionEntry.source,
+                                                           motionEntry.flags, inverseTransform);
         }
     }
 
@@ -442,6 +466,10 @@
                                           motionEntry.xCursorPosition, motionEntry.yCursorPosition,
                                           motionEntry.downTime, motionEntry.pointerProperties,
                                           pointerCoords);
+    if (tracer) {
+        combinedMotionEntry->traceTracker =
+                tracer->traceDerivedEvent(*combinedMotionEntry, *motionEntry.traceTracker);
+    }
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
@@ -531,7 +559,6 @@
 // Returns true if the event type passed as argument represents a user activity.
 bool isUserActivityEvent(const EventEntry& eventEntry) {
     switch (eventEntry.type) {
-        case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::DRAG:
         case EventEntry::Type::FOCUS:
@@ -546,8 +573,8 @@
 }
 
 // Returns true if the given window can accept pointer events at the given display location.
-bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y,
-                          bool isStylus, const ui::Transform& displayTransform) {
+bool windowAcceptsTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
+                          float y, bool isStylus, const ui::Transform& displayTransform) {
     const auto inputConfig = windowInfo.inputConfig;
     if (windowInfo.displayId != displayId ||
         inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
@@ -573,6 +600,18 @@
     return true;
 }
 
+// Returns true if the given window's frame can occlude pointer events at the given display
+// location.
+bool windowOccludesTouchAt(const WindowInfo& windowInfo, ui::LogicalDisplayId displayId, float x,
+                           float y, const ui::Transform& displayTransform) {
+    if (windowInfo.displayId != displayId) {
+        return false;
+    }
+    const auto frame = displayTransform.transform(windowInfo.frame);
+    const auto p = floor(displayTransform.transform(x, y));
+    return p.x >= frame.left && p.x < frame.right && p.y >= frame.top && p.y < frame.bottom;
+}
+
 bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
     return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
             isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
@@ -655,14 +694,15 @@
  */
 std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
                                                     const TouchState& newTouchState,
-                                                    const MotionEntry& entry) {
-    std::vector<TouchedWindow> out;
+                                                    const MotionEntry& entry,
+                                                    std::function<void()> dump) {
     const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
 
     if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
         // ACTION_SCROLL events should not affect the hovering pointer dispatch
         return {};
     }
+    std::vector<TouchedWindow> out;
 
     // We should consider all hovering pointers here. But for now, just use the first one
     const PointerProperties& pointer = entry.pointerProperties[0];
@@ -703,11 +743,13 @@
                     // crashing the device with FATAL.
                     severity = android::base::LogSeverity::ERROR;
                 }
+                dump();
                 LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
             }
             touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
-        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
+        const auto [x, y] = resolveTouchedPosition(entry);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer, x, y);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -778,9 +820,9 @@
 /**
  * Return true if stylus is currently down anywhere on the specified display, and false otherwise.
  */
-bool isStylusActiveInDisplay(
-        int32_t displayId,
-        const std::unordered_map<int32_t /*displayId*/, TouchState>& touchStatesByDisplay) {
+bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId,
+                             const std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                                                      TouchState>& touchStatesByDisplay) {
     const auto it = touchStatesByDisplay.find(displayId);
     if (it == touchStatesByDisplay.end()) {
         return false;
@@ -834,9 +876,54 @@
             return {false, true};
         case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
             return {false, true};
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return {true, false};
     }
 }
 
+class ScopedSyntheticEventTracer {
+public:
+    ScopedSyntheticEventTracer(std::unique_ptr<trace::InputTracerInterface>& tracer)
+          : mTracer(tracer), mProcessingTimestamp(now()) {
+        if (mTracer) {
+            mEventTracker = mTracer->createTrackerForSyntheticEvent();
+        }
+    }
+
+    ~ScopedSyntheticEventTracer() {
+        if (mTracer) {
+            mTracer->eventProcessingComplete(*mEventTracker, mProcessingTimestamp);
+        }
+    }
+
+    const std::unique_ptr<trace::EventTrackerInterface>& getTracker() const {
+        return mEventTracker;
+    }
+
+private:
+    const std::unique_ptr<trace::InputTracerInterface>& mTracer;
+    std::unique_ptr<trace::EventTrackerInterface> mEventTracker;
+    const nsecs_t mProcessingTimestamp;
+};
+
+/**
+ * This is needed to help use "InputEventInjectionResult" with base::Result.
+ */
+template <typename T>
+struct EnumErrorWrapper {
+    T mVal;
+    EnumErrorWrapper(T&& e) : mVal(std::forward<T>(e)) {}
+    operator const T&() const { return mVal; }
+    T value() const { return mVal; }
+    std::string print() const { return ftl::enum_string(mVal); }
+};
+
+Error<EnumErrorWrapper<InputEventInjectionResult>> injectionError(InputEventInjectionResult&& e) {
+    LOG_ALWAYS_FATAL_IF(e == InputEventInjectionResult::SUCCEEDED);
+    return Error<EnumErrorWrapper<InputEventInjectionResult>>(
+            std::forward<InputEventInjectionResult>(e));
+}
+
 } // namespace
 
 // --- InputDispatcher ---
@@ -857,8 +944,9 @@
         mDispatchFrozen(false),
         mInputFilterEnabled(false),
         mMaximumObscuringOpacityForTouch(1.0f),
-        mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
+        mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
+        mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID),
         mLatencyAggregator(),
         mLatencyTracker(&mLatencyAggregator) {
     mLooper = sp<Looper>::make(false);
@@ -1088,14 +1176,6 @@
     }
 
     switch (mPendingEvent->type) {
-        case EventEntry::Type::CONFIGURATION_CHANGED: {
-            const ConfigurationChangedEntry& typedEntry =
-                    static_cast<const ConfigurationChangedEntry&>(*mPendingEvent);
-            done = dispatchConfigurationChangedLocked(currentTime, typedEntry);
-            dropReason = DropReason::NOT_DROPPED; // configuration changes are never dropped
-            break;
-        }
-
         case EventEntry::Type::DEVICE_RESET: {
             const DeviceResetEntry& typedEntry =
                     static_cast<const DeviceResetEntry&>(*mPendingEvent);
@@ -1147,10 +1227,6 @@
                 dropReason = DropReason::BLOCKED;
             }
             done = dispatchKeyLocked(currentTime, keyEntry, &dropReason, nextWakeupTime);
-            if (done && mTracer) {
-                ensureEventTraced(*keyEntry);
-                mTracer->eventProcessingComplete(*keyEntry->traceTracker);
-            }
             break;
         }
 
@@ -1176,10 +1252,6 @@
                 }
             }
             done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
-            if (done && mTracer) {
-                ensureEventTraced(*motionEntry);
-                mTracer->eventProcessingComplete(*motionEntry->traceTracker);
-            }
             break;
         }
 
@@ -1205,6 +1277,12 @@
         }
         mLastDropReason = dropReason;
 
+        if (mTracer) {
+            if (auto& traceTracker = getTraceTracker(*mPendingEvent); traceTracker != nullptr) {
+                mTracer->eventProcessingComplete(*traceTracker, currentTime);
+            }
+        }
+
         releasePendingEventLocked();
         nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
     }
@@ -1227,7 +1305,7 @@
     // If the application takes too long to catch up then we drop all events preceding
     // the touch into the other window.
     if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
-        const int32_t displayId = motionEntry.displayId;
+        const ui::LogicalDisplayId displayId = motionEntry.displayId;
         const auto [x, y] = resolveTouchedPosition(motionEntry);
         const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
 
@@ -1245,7 +1323,7 @@
 
         // Alternatively, maybe there's a spy window that could handle this event.
         const std::vector<sp<WindowInfoHandle>> touchedSpies =
-                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
+                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus, motionEntry.deviceId);
         for (const auto& windowHandle : touchedSpies) {
             const std::shared_ptr<Connection> connection =
                     getConnectionLocked(windowHandle->getToken());
@@ -1329,7 +1407,6 @@
             break;
         }
         case EventEntry::Type::TOUCH_MODE_CHANGED:
-        case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::SENSOR:
         case EventEntry::Type::POINTER_CAPTURE_CHANGED:
@@ -1352,8 +1429,8 @@
     }
 }
 
-sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y,
-                                                                bool isStylus,
+sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(ui::LogicalDisplayId displayId,
+                                                                float x, float y, bool isStylus,
                                                                 bool ignoreDragWindow) const {
     // Traverse windows from front to back to find touched window.
     const auto& windowHandles = getWindowHandlesLocked(displayId);
@@ -1372,7 +1449,8 @@
 }
 
 std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
-        int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
+        ui::LogicalDisplayId displayId, const sp<WindowInfoHandle>& touchedWindow,
+        int32_t pointerId) const {
     if (touchedWindow == nullptr) {
         return {};
     }
@@ -1399,15 +1477,27 @@
 }
 
 std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
-        int32_t displayId, float x, float y, bool isStylus) const {
+        ui::LogicalDisplayId displayId, float x, float y, bool isStylus, DeviceId deviceId) const {
     // Traverse windows from front to back and gather the touched spy windows.
     std::vector<sp<WindowInfoHandle>> spyWindows;
     const auto& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         const WindowInfo& info = *windowHandle->getInfo();
-
         if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
-            continue;
+            // Generally, we would skip any pointer that's outside of the window. However, if the
+            // spy prevents splitting, and already has some of the pointers from this device, then
+            // it should get more pointers from the same device, even if they are outside of that
+            // window
+            if (info.supportsSplitTouch()) {
+                continue;
+            }
+
+            // We know that split touch is not supported. Skip this window only if it doesn't have
+            // any touching pointers for this device already.
+            if (!windowHasTouchingPointersLocked(windowHandle, deviceId)) {
+                continue;
+            }
+            // If it already has pointers down for this device, then give it this pointer, too.
         }
         if (!info.isSpy()) {
             // The first touched non-spy window was found, so return the spy windows touched so far.
@@ -1456,8 +1546,9 @@
 
     switch (entry.type) {
         case EventEntry::Type::KEY: {
-            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason,
+                                       keyEntry.traceTracker);
             options.displayId = keyEntry.displayId;
             options.deviceId = keyEntry.deviceId;
             synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1466,13 +1557,14 @@
         case EventEntry::Type::MOTION: {
             const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
             if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason,
+                                           motionEntry.traceTracker);
                 options.displayId = motionEntry.displayId;
                 options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
             } else {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                           reason);
+                                           reason, motionEntry.traceTracker);
                 options.displayId = motionEntry.displayId;
                 options.deviceId = motionEntry.deviceId;
                 synthesizeCancelationEventsForAllConnectionsLocked(options);
@@ -1488,7 +1580,6 @@
         }
         case EventEntry::Type::FOCUS:
         case EventEntry::Type::TOUCH_MODE_CHANGED:
-        case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET: {
             LOG_ALWAYS_FATAL("Should not drop %s events", ftl::enum_string(entry.type).c_str());
             break;
@@ -1577,24 +1668,6 @@
     return newEntry;
 }
 
-bool InputDispatcher::dispatchConfigurationChangedLocked(nsecs_t currentTime,
-                                                         const ConfigurationChangedEntry& entry) {
-    if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("dispatchConfigurationChanged - eventTime=%" PRId64, entry.eventTime);
-    }
-
-    // Reset key repeating in case a keyboard device was added or removed or something.
-    resetKeyRepeatLocked();
-
-    // Enqueue a command to run outside the lock to tell the policy that the configuration changed.
-    auto command = [this, eventTime = entry.eventTime]() REQUIRES(mLock) {
-        scoped_unlock unlock(mLock);
-        mPolicy.notifyConfigurationChanged(eventTime);
-    };
-    postCommandLocked(std::move(command));
-    return true;
-}
-
 bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime,
                                                 const DeviceResetEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
@@ -1607,7 +1680,9 @@
         resetKeyRepeatLocked();
     }
 
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset",
+                               traceContext.getTracker());
     options.deviceId = entry.deviceId;
     synthesizeCancelationEventsForAllConnectionsLocked(options);
 
@@ -1664,7 +1739,7 @@
     const bool haveWindowWithPointerCapture = mWindowTokenWithPointerCapture != nullptr;
     sp<IBinder> token;
 
-    if (entry->pointerCaptureRequest.enable) {
+    if (entry->pointerCaptureRequest.isEnable()) {
         // Enable Pointer Capture.
         if (haveWindowWithPointerCapture &&
             (entry->pointerCaptureRequest == mCurrentPointerCaptureRequest)) {
@@ -1673,7 +1748,7 @@
             ALOGI("Skipping dispatch of Pointer Capture being enabled: no state change.");
             return;
         }
-        if (!mCurrentPointerCaptureRequest.enable) {
+        if (!mCurrentPointerCaptureRequest.isEnable()) {
             // This can happen if a window requests capture and immediately releases capture.
             ALOGW("No window requested Pointer Capture.");
             dropReason = DropReason::NO_POINTER_CAPTURE;
@@ -1686,6 +1761,8 @@
 
         token = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
         LOG_ALWAYS_FATAL_IF(!token, "Cannot find focused window for Pointer Capture.");
+        LOG_ALWAYS_FATAL_IF(token != entry->pointerCaptureRequest.window,
+                            "Unexpected requested window for Pointer Capture.");
         mWindowTokenWithPointerCapture = token;
     } else {
         // Disable Pointer Capture.
@@ -1705,8 +1782,8 @@
         }
         token = mWindowTokenWithPointerCapture;
         mWindowTokenWithPointerCapture = nullptr;
-        if (mCurrentPointerCaptureRequest.enable) {
-            setPointerCaptureLocked(false);
+        if (mCurrentPointerCaptureRequest.isEnable()) {
+            setPointerCaptureLocked(nullptr);
         }
     }
 
@@ -1714,8 +1791,8 @@
     if (connection == nullptr) {
         // Window has gone away, clean up Pointer Capture state.
         mWindowTokenWithPointerCapture = nullptr;
-        if (mCurrentPointerCaptureRequest.enable) {
-            setPointerCaptureLocked(false);
+        if (mCurrentPointerCaptureRequest.isEnable()) {
+            setPointerCaptureLocked(nullptr);
         }
         return;
     }
@@ -1762,7 +1839,7 @@
                                         DropReason* dropReason, nsecs_t& nextWakeupTime) {
     // Preprocessing.
     if (!entry->dispatchInProgress) {
-        if (entry->repeatCount == 0 && entry->action == AKEY_EVENT_ACTION_DOWN &&
+        if (!entry->syntheticRepeat && entry->action == AKEY_EVENT_ACTION_DOWN &&
             (entry->policyFlags & POLICY_FLAG_TRUSTED) &&
             (!(entry->policyFlags & POLICY_FLAG_DISABLE_KEY_REPEAT))) {
             if (mKeyRepeatState.lastKeyEntry &&
@@ -1827,8 +1904,6 @@
                 doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
             };
             postCommandLocked(std::move(command));
-            // Poke user activity for keys not passed to user
-            pokeUserActivityLocked(*entry);
             return false; // wait for the command to run
         } else {
             entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
@@ -1845,26 +1920,31 @@
                            *dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
                                                              : InputEventInjectionResult::FAILED);
         mReporter->reportDroppedKey(entry->id);
-        // Poke user activity for undispatched keys
-        pokeUserActivityLocked(*entry);
+        // Poke user activity for consumed keys, as it may have not been reported due to
+        // the focused window requesting user activity to be disabled
+        if (*dropReason == DropReason::POLICY &&
+            mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
+            pokeUserActivityLocked(*entry);
+        }
         return true;
     }
 
     // Identify targets.
-    InputEventInjectionResult injectionResult;
-    sp<WindowInfoHandle> focusedWindow =
-            findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime,
-                                          /*byref*/ injectionResult);
-    if (injectionResult == InputEventInjectionResult::PENDING) {
-        return false;
-    }
+    Result<sp<WindowInfoHandle>, InputEventInjectionResult> result =
+            findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime);
 
-    setInjectionResult(*entry, injectionResult);
-    if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
+    if (!result.ok()) {
+        if (result.error().code() == InputEventInjectionResult::PENDING) {
+            return false;
+        }
+        setInjectionResult(*entry, result.error().code());
         return true;
     }
+    sp<WindowInfoHandle>& focusedWindow = *result;
     LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
 
+    setInjectionResult(*entry, InputEventInjectionResult::SUCCEEDED);
+
     std::vector<InputTarget> inputTargets;
     addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
                           InputTarget::Flags::FOREGROUND, getDownTime(*entry), inputTargets);
@@ -1886,12 +1966,12 @@
 
 void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32 ", "
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%s, "
               "policyFlags=0x%x, action=0x%x, flags=0x%x, keyCode=0x%x, scanCode=0x%x, "
               "metaState=0x%x, repeatCount=%d, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, entry.action, entry.flags, entry.keyCode, entry.scanCode,
-              entry.metaState, entry.repeatCount, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId, entry.source,
+              entry.displayId.toString().c_str(), entry.policyFlags, entry.action, entry.flags,
+              entry.keyCode, entry.scanCode, entry.metaState, entry.repeatCount, entry.downTime);
     }
 }
 
@@ -1969,19 +2049,28 @@
             pilferPointersLocked(mDragState->dragWindow->getToken());
         }
 
-        inputTargets =
-                findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult);
-        LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED &&
-                            !inputTargets.empty());
+        Result<std::vector<InputTarget>, InputEventInjectionResult> result =
+                findTouchedWindowTargetsLocked(currentTime, *entry);
+
+        if (result.ok()) {
+            inputTargets = std::move(*result);
+            injectionResult = InputEventInjectionResult::SUCCEEDED;
+        } else {
+            injectionResult = result.error().code();
+        }
     } else {
         // Non touch event.  (eg. trackball)
-        sp<WindowInfoHandle> focusedWindow =
-                findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, injectionResult);
-        if (injectionResult == InputEventInjectionResult::SUCCEEDED) {
+        Result<sp<WindowInfoHandle>, InputEventInjectionResult> result =
+                findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime);
+        if (result.ok()) {
+            sp<WindowInfoHandle>& focusedWindow = *result;
             LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
             addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
                                   InputTarget::Flags::FOREGROUND, getDownTime(*entry),
                                   inputTargets);
+            injectionResult = InputEventInjectionResult::SUCCEEDED;
+        } else {
+            injectionResult = result.error().code();
         }
     }
     if (injectionResult == InputEventInjectionResult::PENDING) {
@@ -1996,7 +2085,7 @@
         CancelationOptions::Mode mode(
                 isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
                                : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
-        CancelationOptions options(mode, "input event injection failed");
+        CancelationOptions options(mode, "input event injection failed", entry->traceTracker);
         options.displayId = entry->displayId;
         synthesizeCancelationEventsForMonitorsLocked(options);
         return true;
@@ -2040,22 +2129,18 @@
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
-              ", policyFlags=0x%x, "
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%s, policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
-              "metaState=0x%x, buttonState=0x%x,"
-              "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
+              "metaState=0x%x, buttonState=0x%x, downTime=%" PRId64,
               prefix, entry.eventTime, entry.deviceId,
-              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
-              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
-              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
-              entry.yPrecision, entry.downTime);
+              inputEventSourceToString(entry.source).c_str(), entry.displayId.toString().c_str(),
+              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
+              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.downTime);
 
         for (uint32_t i = 0; i < entry.getPointerCount(); 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",
+                  "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
                   i, entry.pointerProperties[i].id,
                   ftl::enum_string(entry.pointerProperties[i].toolType).c_str(),
                   entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
@@ -2102,8 +2187,9 @@
     if (connection->status != Connection::Status::NORMAL) {
         return;
     }
+    ScopedSyntheticEventTracer traceContext(mTracer);
     CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
-                               "application not responding");
+                               "application not responding", traceContext.getTracker());
 
     sp<WindowInfoHandle> windowHandle;
     if (!connection->monitor) {
@@ -2133,8 +2219,8 @@
  * then it should be dispatched to that display. Otherwise, the event goes to the focused display.
  * Focused display is the display that the user most recently interacted with.
  */
-int32_t InputDispatcher::getTargetDisplayId(const EventEntry& entry) {
-    int32_t displayId;
+ui::LogicalDisplayId InputDispatcher::getTargetDisplayId(const EventEntry& entry) {
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     switch (entry.type) {
         case EventEntry::Type::KEY: {
             const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
@@ -2149,15 +2235,14 @@
         case EventEntry::Type::TOUCH_MODE_CHANGED:
         case EventEntry::Type::POINTER_CAPTURE_CHANGED:
         case EventEntry::Type::FOCUS:
-        case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET:
         case EventEntry::Type::SENSOR:
         case EventEntry::Type::DRAG: {
             ALOGE("%s events do not have a target display", ftl::enum_string(entry.type).c_str());
-            return ADISPLAY_ID_NONE;
+            return ui::LogicalDisplayId::INVALID;
         }
     }
-    return displayId == ADISPLAY_ID_NONE ? mFocusedDisplayId : displayId;
+    return displayId == ui::LogicalDisplayId::INVALID ? mFocusedDisplayId : displayId;
 }
 
 bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime,
@@ -2191,12 +2276,10 @@
     return false;
 }
 
-sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
-        nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
-        InputEventInjectionResult& outInjectionResult) {
-    outInjectionResult = InputEventInjectionResult::FAILED; // Default result
-
-    int32_t displayId = getTargetDisplayId(entry);
+Result<sp<WindowInfoHandle>, InputEventInjectionResult>
+InputDispatcher::findFocusedWindowTargetLocked(nsecs_t currentTime, const EventEntry& entry,
+                                               nsecs_t& nextWakeupTime) {
+    ui::LogicalDisplayId displayId = getTargetDisplayId(entry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     std::shared_ptr<InputApplicationHandle> focusedApplicationHandle =
             getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
@@ -2205,14 +2288,14 @@
     // then drop the event.
     if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
         ALOGI("Dropping %s event because there is no focused window or focused application in "
-              "display %" PRId32 ".",
-              ftl::enum_string(entry.type).c_str(), displayId);
-        return nullptr;
+              "display %s.",
+              ftl::enum_string(entry.type).c_str(), displayId.toString().c_str());
+        return injectionError(InputEventInjectionResult::FAILED);
     }
 
     // Drop key events if requested by input feature
     if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
-        return nullptr;
+        return injectionError(InputEventInjectionResult::FAILED);
     }
 
     // Compatibility behavior: raise ANR if there is a focused application, but no focused window.
@@ -2232,17 +2315,15 @@
                   "window when it finishes starting up. Will wait for %" PRId64 "ms",
                   mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
             nextWakeupTime = std::min(nextWakeupTime, *mNoFocusedWindowTimeoutTime);
-            outInjectionResult = InputEventInjectionResult::PENDING;
-            return nullptr;
+            return injectionError(InputEventInjectionResult::PENDING);
         } else if (currentTime > *mNoFocusedWindowTimeoutTime) {
             // Already raised ANR. Drop the event
             ALOGE("Dropping %s event because there is no focused window",
                   ftl::enum_string(entry.type).c_str());
-            return nullptr;
+            return injectionError(InputEventInjectionResult::FAILED);
         } else {
             // Still waiting for the focused window
-            outInjectionResult = InputEventInjectionResult::PENDING;
-            return nullptr;
+            return injectionError(InputEventInjectionResult::PENDING);
         }
     }
 
@@ -2252,15 +2333,13 @@
     // Verify targeted injection.
     if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
         ALOGW("Dropping injected event: %s", (*err).c_str());
-        outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
-        return nullptr;
+        return injectionError(InputEventInjectionResult::TARGET_MISMATCH);
     }
 
     if (focusedWindowHandle->getInfo()->inputConfig.test(
                 WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
         ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
-        outInjectionResult = InputEventInjectionResult::PENDING;
-        return nullptr;
+        return injectionError(InputEventInjectionResult::PENDING);
     }
 
     // If the event is a key event, then we must wait for all previous events to
@@ -2277,12 +2356,10 @@
     if (entry.type == EventEntry::Type::KEY) {
         if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
             nextWakeupTime = std::min(nextWakeupTime, *mKeyIsWaitingForEventsTimeout);
-            outInjectionResult = InputEventInjectionResult::PENDING;
-            return nullptr;
+            return injectionError(InputEventInjectionResult::PENDING);
         }
     }
-
-    outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+    // Success!
     return focusedWindowHandle;
 }
 
@@ -2306,21 +2383,17 @@
     return responsiveMonitors;
 }
 
-std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
-        nsecs_t currentTime, const MotionEntry& entry,
-        InputEventInjectionResult& outInjectionResult) {
+base::Result<std::vector<InputTarget>, android::os::InputEventInjectionResult>
+InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry& entry) {
     ATRACE_CALL();
 
     std::vector<InputTarget> targets;
     // For security reasons, we defer updating the touch state until we are sure that
     // event injection will be allowed.
-    const int32_t displayId = entry.displayId;
+    const ui::LogicalDisplayId displayId = entry.displayId;
     const int32_t action = entry.action;
     const int32_t maskedAction = MotionEvent::getActionMasked(action);
 
-    // Update the touch state as needed based on the properties of the touch event.
-    outInjectionResult = InputEventInjectionResult::PENDING;
-
     // Copy current touch state into tempTouchState.
     // This state will be used to update mTouchStatesByDisplay at the end of this function.
     // If no state for the specified display exists, then our initial state will be empty.
@@ -2360,8 +2433,7 @@
             // Started hovering, but the device is already down: reject the hover event
             LOG(ERROR) << "Got hover event " << entry.getDescription()
                        << " but the device is already down " << oldState->dump();
-            outInjectionResult = InputEventInjectionResult::FAILED;
-            return {};
+            return injectionError(InputEventInjectionResult::FAILED);
         }
         // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
         // all of the existing hovering pointers and recompute.
@@ -2382,19 +2454,25 @@
         if (isDown) {
             targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointer.id);
         }
+        LOG_IF(INFO, newTouchedWindowHandle == nullptr)
+                << "No new touched window at (" << std::format("{:.1f}, {:.1f}", x, y)
+                << ") in display " << displayId;
         // Handle the case where we did not find a window.
-        if (newTouchedWindowHandle == nullptr) {
-            ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
-            // Try to assign the pointer to the first foreground window we find, if there is one.
-            newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
+        if (!input_flags::split_all_touches()) {
+            // If we are force splitting all touches, then touches outside of the window should
+            // be dropped, even if this device already has pointers down in another window.
+            if (newTouchedWindowHandle == nullptr) {
+                // Try to assign the pointer to the first foreground window we find, if there is
+                // one.
+                newTouchedWindowHandle =
+                        tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
+            }
         }
 
         // Verify targeted injection.
         if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
             ALOGW("Dropping injected touch event: %s", (*err).c_str());
-            outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
-            newTouchedWindowHandle = nullptr;
-            return {};
+            return injectionError(InputEventInjectionResult::TARGET_MISMATCH);
         }
 
         // Figure out whether splitting will be allowed for this window.
@@ -2417,18 +2495,16 @@
         }
 
         std::vector<sp<WindowInfoHandle>> newTouchedWindows =
-                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
+                findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus, entry.deviceId);
         if (newTouchedWindowHandle != nullptr) {
             // Process the foreground window first so that it is the first to receive the event.
             newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
         }
 
         if (newTouchedWindows.empty()) {
-            ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
-                  "%d.",
-                  x, y, displayId);
-            outInjectionResult = InputEventInjectionResult::FAILED;
-            return {};
+            LOG(INFO) << "Dropping event because there is no touchable window at (" << x << ", "
+                      << y << ") on display " << displayId << ": " << entry;
+            return injectionError(InputEventInjectionResult::FAILED);
         }
 
         for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
@@ -2438,7 +2514,8 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer, x,
+                                                          y);
             }
 
             // Set target flags.
@@ -2463,11 +2540,19 @@
             if (!isHoverAction) {
                 const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
                         maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
-                tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                 targetFlags, entry.deviceId, {pointer},
-                                                 isDownOrPointerDown
-                                                         ? std::make_optional(entry.eventTime)
-                                                         : std::nullopt);
+                Result<void> addResult =
+                        tempTouchState.addOrUpdateWindow(windowHandle,
+                                                         InputTarget::DispatchMode::AS_IS,
+                                                         targetFlags, entry.deviceId, {pointer},
+                                                         isDownOrPointerDown
+                                                                 ? std::make_optional(
+                                                                           entry.eventTime)
+                                                                 : std::nullopt);
+                if (!addResult.ok()) {
+                    LOG(ERROR) << "Error while processing " << entry << " for "
+                               << windowHandle->getName();
+                    logDispatchStateLocked();
+                }
                 // If this is the pointer going down and the touched window has a wallpaper
                 // then also add the touched wallpaper windows so they are locked in for the
                 // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
@@ -2520,8 +2605,7 @@
                           << " is not down or we previously dropped the pointer down event in "
                           << "display " << displayId << ": " << entry.getDescription();
             }
-            outInjectionResult = InputEventInjectionResult::FAILED;
-            return {};
+            return injectionError(InputEventInjectionResult::FAILED);
         }
 
         // If the pointer is not currently hovering, then ignore the event.
@@ -2532,8 +2616,7 @@
                 LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in "
                              "display "
                           << displayId << ": " << entry.getDescription();
-                outInjectionResult = InputEventInjectionResult::FAILED;
-                return {};
+                return injectionError(InputEventInjectionResult::FAILED);
             }
             tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
         }
@@ -2542,11 +2625,11 @@
 
         // Check whether touches should slip outside of the current foreground window.
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
-            tempTouchState.isSlippery()) {
+            tempTouchState.isSlippery(entry.deviceId)) {
             const auto [x, y] = resolveTouchedPosition(entry);
             const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
             sp<WindowInfoHandle> oldTouchedWindowHandle =
-                    tempTouchState.getFirstForegroundWindowHandle();
+                    tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
                     findTouchedWindowAtLocked(displayId, x, y, isStylus);
@@ -2554,8 +2637,7 @@
             // Verify targeted injection.
             if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
                 ALOGW("Dropping injected event: %s", (*err).c_str());
-                outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
-                return {};
+                return injectionError(InputEventInjectionResult::TARGET_MISMATCH);
             }
 
             // Do not slide events to the window which can not receive motion event
@@ -2566,9 +2648,9 @@
 
             if (newTouchedWindowHandle != nullptr &&
                 !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
-                ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
+                ALOGI("Touch is slipping out of window %s into window %s in display %s",
                       oldTouchedWindowHandle->getName().c_str(),
-                      newTouchedWindowHandle->getName().c_str(), displayId);
+                      newTouchedWindowHandle->getName().c_str(), displayId.toString().c_str());
 
                 // Make a slippery exit from the old window.
                 std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -2608,7 +2690,7 @@
 
                 // Check if the wallpaper window should deliver the corresponding event.
                 slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
-                                   tempTouchState, entry.deviceId, pointer, targets);
+                                   tempTouchState, entry, targets);
                 tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
@@ -2635,20 +2717,17 @@
     // Update dispatching for hover enter and exit.
     {
         std::vector<TouchedWindow> hoveringWindows =
-                getHoveringWindowsLocked(oldState, tempTouchState, entry);
+                getHoveringWindowsLocked(oldState, tempTouchState, entry,
+                                         std::bind_front(&InputDispatcher::logDispatchStateLocked,
+                                                         this));
+        // Hardcode to single hovering pointer for now.
+        std::bitset<MAX_POINTER_ID + 1> pointerIds;
+        pointerIds.set(entry.pointerProperties[0].id);
         for (const TouchedWindow& touchedWindow : hoveringWindows) {
-            std::optional<InputTarget> target =
-                    createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
-                                            touchedWindow.targetFlags,
-                                            touchedWindow.getDownTimeInTarget(entry.deviceId));
-            if (!target) {
-                continue;
-            }
-            // Hardcode to single hovering pointer for now.
-            std::bitset<MAX_POINTER_ID + 1> pointerIds;
-            pointerIds.set(entry.pointerProperties[0].id);
-            target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform);
-            targets.push_back(*target);
+            addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+                                         touchedWindow.targetFlags, pointerIds,
+                                         touchedWindow.getDownTimeInTarget(entry.deviceId),
+                                         targets);
         }
     }
 
@@ -2663,8 +2742,7 @@
             ALOGW("Dropping targeted injection: At least one touched window is not owned by uid "
                   "%s:%s",
                   entry.injectionState->targetUid->toString().c_str(), errs.c_str());
-            outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
-            return {};
+            return injectionError(InputEventInjectionResult::TARGET_MISMATCH);
         }
     }
 
@@ -2672,7 +2750,7 @@
     // has a different UID, then we will not reveal coordinate information to this window.
     if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
         sp<WindowInfoHandle> foregroundWindowHandle =
-                tempTouchState.getFirstForegroundWindowHandle();
+                tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
         if (foregroundWindowHandle) {
             const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
             for (InputTarget& target : targets) {
@@ -2721,8 +2799,7 @@
 
     if (targets.empty()) {
         LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription();
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {};
+        return injectionError(InputEventInjectionResult::FAILED);
     }
 
     // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no
@@ -2732,12 +2809,9 @@
         })) {
         LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: "
                   << entry.getDescription();
-        outInjectionResult = InputEventInjectionResult::FAILED;
-        return {};
+        return injectionError(InputEventInjectionResult::FAILED);
     }
 
-    outInjectionResult = InputEventInjectionResult::SUCCEEDED;
-
     // Now that we have generated all of the input targets for this event, reset the dispatch
     // mode for all touched window to AS_IS.
     for (TouchedWindow& touchedWindow : tempTouchState.windows) {
@@ -2761,7 +2835,7 @@
     // Save changes unless the action was scroll in which case the temporary touch
     // state was only valid for this one action.
     if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
-        if (displayId >= 0) {
+        if (displayId >= ui::LogicalDisplayId::DEFAULT) {
             tempTouchState.clearWindowsWithoutPointers();
             mTouchStatesByDisplay[displayId] = tempTouchState;
         } else {
@@ -2776,7 +2850,7 @@
     return targets;
 }
 
-void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
+void InputDispatcher::finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) {
     // Prevent stylus interceptor windows from affecting drag and drop behavior for now, until we
     // have an explicit reason to support it.
     constexpr bool isStylus = false;
@@ -2794,7 +2868,8 @@
 }
 
 void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
-    if (!mDragState || mDragState->dragWindow->getInfo()->displayId != entry.displayId) {
+    if (!mDragState || mDragState->dragWindow->getInfo()->displayId != entry.displayId ||
+        mDragState->deviceId != entry.deviceId) {
         return;
     }
 
@@ -2985,11 +3060,15 @@
                    << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
     }
 
-    it->addPointers(pointerIds, windowInfo->transform);
+    Result<void> result = it->addPointers(pointerIds, windowInfo->transform);
+    if (!result.ok()) {
+        logDispatchStateLocked();
+        LOG(FATAL) << result.error().message();
+    }
 }
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
-                                                       int32_t displayId) {
+                                                       ui::LogicalDisplayId displayId) {
     auto monitorsIt = mGlobalMonitorsByDisplay.find(displayId);
     if (monitorsIt == mGlobalMonitorsByDisplay.end()) return;
 
@@ -3059,9 +3138,9 @@
  * If neither of those is true, then it means the touch can be allowed.
  */
 InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLocked(
-        const sp<WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const {
+        const sp<WindowInfoHandle>& windowHandle, float x, float y) const {
     const WindowInfo* windowInfo = windowHandle->getInfo();
-    int32_t displayId = windowInfo->displayId;
+    ui::LogicalDisplayId displayId = windowInfo->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     TouchOcclusionInfo info;
     info.hasBlockingOcclusion = false;
@@ -3073,7 +3152,8 @@
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
-        if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->frameContainsPoint(x, y) &&
+        if (canBeObscuredBy(windowHandle, otherHandle) &&
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId)) &&
             !haveSameApplicationToken(windowInfo, otherInfo)) {
             if (DEBUG_TOUCH_OCCLUSION) {
                 info.debugInfo.push_back(
@@ -3143,8 +3223,8 @@
 }
 
 bool InputDispatcher::isWindowObscuredAtPointLocked(const sp<WindowInfoHandle>& windowHandle,
-                                                    int32_t x, int32_t y) const {
-    int32_t displayId = windowHandle->getInfo()->displayId;
+                                                    float x, float y) const {
+    ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
         if (windowHandle == otherHandle) {
@@ -3152,7 +3232,7 @@
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
         if (canBeObscuredBy(windowHandle, otherHandle) &&
-            otherInfo->frameContainsPoint(x, y)) {
+            windowOccludesTouchAt(*otherInfo, displayId, x, y, getTransformLocked(displayId))) {
             return true;
         }
     }
@@ -3160,7 +3240,7 @@
 }
 
 bool InputDispatcher::isWindowObscuredLocked(const sp<WindowInfoHandle>& windowHandle) const {
-    int32_t displayId = windowHandle->getInfo()->displayId;
+    ui::LogicalDisplayId displayId = windowHandle->getInfo()->displayId;
     const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     const WindowInfo* windowInfo = windowHandle->getInfo();
     for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
@@ -3168,8 +3248,7 @@
             break; // All future windows are below us. Exit early.
         }
         const WindowInfo* otherInfo = otherHandle->getInfo();
-        if (canBeObscuredBy(windowHandle, otherHandle) &&
-            otherInfo->overlaps(windowInfo)) {
+        if (canBeObscuredBy(windowHandle, otherHandle) && otherInfo->overlaps(windowInfo)) {
             return true;
         }
     }
@@ -3211,7 +3290,7 @@
         }
     }
 
-    int32_t displayId = getTargetDisplayId(eventEntry);
+    ui::LogicalDisplayId displayId = getTargetDisplayId(eventEntry);
     sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
     const WindowInfo* windowDisablingUserActivityInfo = nullptr;
     if (focusedWindowHandle != nullptr) {
@@ -3241,22 +3320,16 @@
             if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
                 return;
             }
-            // If the key code is unknown, we don't consider it user activity
-            if (keyEntry.keyCode == AKEYCODE_UNKNOWN) {
-                return;
-            }
             // Don't inhibit events that were intercepted or are not passed to
             // the apps, like system shortcuts
             if (windowDisablingUserActivityInfo != nullptr &&
-                keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP &&
-                keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) {
+                keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP) {
                 if (DEBUG_DISPATCH_CYCLE) {
                     ALOGD("Not poking user activity: disabled by window '%s'.",
                           windowDisablingUserActivityInfo->name.c_str());
                 }
                 return;
             }
-
             break;
         }
         default: {
@@ -3376,7 +3449,7 @@
     // Enqueue a new dispatch entry onto the outbound queue for this connection.
     std::unique_ptr<DispatchEntry> dispatchEntry =
             createDispatchEntry(mIdGenerator, inputTarget, eventEntry, inputTarget.flags,
-                                mWindowInfosVsyncId);
+                                mWindowInfosVsyncId, mTracer.get());
 
     // Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
     // different EventEntry than what was passed in.
@@ -3459,21 +3532,31 @@
                             usingCoords = pointerInfo->second;
                         }
                     }
-                    // Generate a new MotionEntry with a new eventId using the resolved action and
-                    // flags.
-                    resolvedMotion = std::make_shared<
-                            MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
-                                         motionEntry.eventTime, motionEntry.deviceId,
-                                         motionEntry.source, motionEntry.displayId,
-                                         motionEntry.policyFlags, resolvedAction,
-                                         motionEntry.actionButton, resolvedFlags,
-                                         motionEntry.metaState, motionEntry.buttonState,
-                                         motionEntry.classification, motionEntry.edgeFlags,
-                                         motionEntry.xPrecision, motionEntry.yPrecision,
-                                         motionEntry.xCursorPosition, motionEntry.yCursorPosition,
-                                         motionEntry.downTime,
-                                         usingProperties.value_or(motionEntry.pointerProperties),
-                                         usingCoords.value_or(motionEntry.pointerCoords));
+                    {
+                        // Generate a new MotionEntry with a new eventId using the resolved action
+                        // and flags, and set it as the resolved entry.
+                        auto newEntry = std::make_shared<
+                                MotionEntry>(mIdGenerator.nextId(), motionEntry.injectionState,
+                                             motionEntry.eventTime, motionEntry.deviceId,
+                                             motionEntry.source, motionEntry.displayId,
+                                             motionEntry.policyFlags, resolvedAction,
+                                             motionEntry.actionButton, resolvedFlags,
+                                             motionEntry.metaState, motionEntry.buttonState,
+                                             motionEntry.classification, motionEntry.edgeFlags,
+                                             motionEntry.xPrecision, motionEntry.yPrecision,
+                                             motionEntry.xCursorPosition,
+                                             motionEntry.yCursorPosition, motionEntry.downTime,
+                                             usingProperties.value_or(
+                                                     motionEntry.pointerProperties),
+                                             usingCoords.value_or(motionEntry.pointerCoords));
+                        if (mTracer) {
+                            ensureEventTraced(motionEntry);
+                            newEntry->traceTracker =
+                                    mTracer->traceDerivedEvent(*newEntry,
+                                                               *motionEntry.traceTracker);
+                        }
+                        resolvedMotion = newEntry;
+                    }
                     if (ATRACE_ENABLED()) {
                         std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
                                                            ") to MotionEvent(id=0x%" PRIx32 ").",
@@ -3496,9 +3579,14 @@
                 LOG(INFO) << "Canceling pointers for device " << resolvedMotion->deviceId << " in "
                           << connection->getInputChannelName() << " with event "
                           << cancelEvent->getDescription();
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*cancelEvent).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelEvent, *resolvedMotion->traceTracker);
+                }
                 std::unique_ptr<DispatchEntry> cancelDispatchEntry =
                         createDispatchEntry(mIdGenerator, inputTarget, std::move(cancelEvent),
-                                            ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId);
+                                            ftl::Flags<InputTarget::Flags>(), mWindowInfosVsyncId,
+                                            mTracer.get());
 
                 // Send these cancel events to the queue before sending the event from the new
                 // device.
@@ -3532,7 +3620,6 @@
             LOG_ALWAYS_FATAL("SENSOR events should not go to apps via input channel");
             break;
         }
-        case EventEntry::Type::CONFIGURATION_CHANGED:
         case EventEntry::Type::DEVICE_RESET: {
             LOG_ALWAYS_FATAL("%s events should not go to apps",
                              ftl::enum_string(eventEntry->type).c_str());
@@ -3732,7 +3819,8 @@
                                                   keyEntry.metaState, keyEntry.repeatCount,
                                                   keyEntry.downTime, keyEntry.eventTime);
                 if (mTracer) {
-                    mTracer->traceEventDispatch(*dispatchEntry, keyEntry.traceTracker.get());
+                    ensureEventTraced(keyEntry);
+                    mTracer->traceEventDispatch(*dispatchEntry, *keyEntry.traceTracker);
                 }
                 break;
             }
@@ -3744,8 +3832,13 @@
                 }
                 const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
                 status = publishMotionEvent(*connection, *dispatchEntry);
+                if (status == BAD_VALUE) {
+                    logDispatchStateLocked();
+                    LOG(FATAL) << "Publisher failed for " << motionEntry;
+                }
                 if (mTracer) {
-                    mTracer->traceEventDispatch(*dispatchEntry, motionEntry.traceTracker.get());
+                    ensureEventTraced(motionEntry);
+                    mTracer->traceEventDispatch(*dispatchEntry, *motionEntry.traceTracker);
                 }
                 break;
             }
@@ -3771,9 +3864,10 @@
             case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
                 const auto& captureEntry =
                         static_cast<const PointerCaptureChangedEntry&>(eventEntry);
-                status = connection->inputPublisher
-                                 .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
-                                                      captureEntry.pointerCaptureRequest.enable);
+                status =
+                        connection->inputPublisher
+                                .publishCaptureEvent(dispatchEntry->seq, captureEntry.id,
+                                                     captureEntry.pointerCaptureRequest.isEnable());
                 break;
             }
 
@@ -3786,7 +3880,6 @@
                 break;
             }
 
-            case EventEntry::Type::CONFIGURATION_CHANGED:
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::SENSOR: {
                 LOG_ALWAYS_FATAL("Should never start dispatch cycles for %s events",
@@ -4124,6 +4217,11 @@
 
         switch (cancelationEventEntry->type) {
             case EventEntry::Type::KEY: {
+                if (mTracer) {
+                    static_cast<KeyEntry&>(*cancelationEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelationEventEntry,
+                                                       *options.traceTracker);
+                }
                 const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
                 if (window) {
                     addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
@@ -4135,6 +4233,11 @@
                 break;
             }
             case EventEntry::Type::MOTION: {
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*cancelationEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*cancelationEventEntry,
+                                                       *options.traceTracker);
+                }
                 const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
                 if (window) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -4172,7 +4275,6 @@
                                  ftl::enum_string(cancelationEventEntry->type).c_str());
                 break;
             }
-            case EventEntry::Type::CONFIGURATION_CHANGED:
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::SENSOR: {
                 LOG_ALWAYS_FATAL("%s event should not be found inside Connections's queue",
@@ -4182,6 +4284,9 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        if (mTracer) {
+            mTracer->dispatchToTargetHint(*options.traceTracker, targets[0]);
+        }
         enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]);
     }
 
@@ -4193,7 +4298,8 @@
 
 void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
         const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
-        ftl::Flags<InputTarget::Flags> targetFlags) {
+        ftl::Flags<InputTarget::Flags> targetFlags,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
     if (connection->status != Connection::Status::NORMAL) {
         return;
     }
@@ -4222,6 +4328,10 @@
         std::vector<InputTarget> targets{};
         switch (downEventEntry->type) {
             case EventEntry::Type::MOTION: {
+                if (mTracer) {
+                    static_cast<MotionEntry&>(*downEventEntry).traceTracker =
+                            mTracer->traceDerivedEvent(*downEventEntry, *traceTracker);
+                }
                 const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
                 if (windowHandle != nullptr) {
                     std::bitset<MAX_POINTER_ID + 1> pointerIds;
@@ -4247,7 +4357,6 @@
             case EventEntry::Type::KEY:
             case EventEntry::Type::FOCUS:
             case EventEntry::Type::TOUCH_MODE_CHANGED:
-            case EventEntry::Type::CONFIGURATION_CHANGED:
             case EventEntry::Type::DEVICE_RESET:
             case EventEntry::Type::POINTER_CAPTURE_CHANGED:
             case EventEntry::Type::SENSOR:
@@ -4259,6 +4368,9 @@
         }
 
         if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+        if (mTracer) {
+            mTracer->dispatchToTargetHint(*traceTracker, targets[0]);
+        }
         enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]);
     }
 
@@ -4271,72 +4383,27 @@
 std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
         const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
         nsecs_t splitDownTime) {
-    ALOG_ASSERT(pointerIds.any());
-
-    uint32_t splitPointerIndexMap[MAX_POINTERS];
-    std::vector<PointerProperties> splitPointerProperties;
-    std::vector<PointerCoords> splitPointerCoords;
-
-    uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
-    uint32_t splitPointerCount = 0;
-
-    for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
-         originalPointerIndex++) {
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
-            splitPointerProperties.push_back(pointerProperties);
-            splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
-            splitPointerCount += 1;
-        }
-    }
-
-    if (splitPointerCount != pointerIds.count()) {
+    const auto& [action, pointerProperties, pointerCoords] =
+            MotionEvent::split(originalMotionEntry.action, originalMotionEntry.flags,
+                               /*historySize=*/0, originalMotionEntry.pointerProperties,
+                               originalMotionEntry.pointerCoords, pointerIds);
+    if (pointerIds.count() != pointerCoords.size()) {
+        // TODO(b/329107108): Determine why some IDs in pointerIds were not in originalMotionEntry.
         // This is bad.  We are missing some of the pointers that we expected to deliver.
         // Most likely this indicates that we received an ACTION_MOVE events that has
         // different pointer ids than we expected based on the previous ACTION_DOWN
         // or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
         // in this way.
-        ALOGW("Dropping split motion event because the pointer count is %d but "
+        ALOGW("Dropping split motion event because the pointer count is %zu but "
               "we expected there to be %zu pointers.  This probably means we received "
               "a broken sequence of pointer ids from the input device: %s",
-              splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str());
+              pointerCoords.size(), pointerIds.count(),
+              originalMotionEntry.getDescription().c_str());
         return nullptr;
     }
 
-    int32_t action = originalMotionEntry.action;
-    int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
-    if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
-        maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
-        int32_t originalPointerIndex = MotionEvent::getActionIndex(action);
-        const PointerProperties& pointerProperties =
-                originalMotionEntry.pointerProperties[originalPointerIndex];
-        uint32_t pointerId = uint32_t(pointerProperties.id);
-        if (pointerIds.test(pointerId)) {
-            if (pointerIds.count() == 1) {
-                // The first/last pointer went down/up.
-                action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
-                        ? AMOTION_EVENT_ACTION_DOWN
-                        : (originalMotionEntry.flags & AMOTION_EVENT_FLAG_CANCELED) != 0
-                                ? AMOTION_EVENT_ACTION_CANCEL
-                                : AMOTION_EVENT_ACTION_UP;
-            } else {
-                // A secondary pointer went down/up.
-                uint32_t splitPointerIndex = 0;
-                while (pointerId != uint32_t(splitPointerProperties[splitPointerIndex].id)) {
-                    splitPointerIndex += 1;
-                }
-                action = maskedAction |
-                        (splitPointerIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-            }
-        } else {
-            // An unrelated pointer changed.
-            action = AMOTION_EVENT_ACTION_MOVE;
-        }
-    }
-
+    // TODO(b/327503168): Move this check inside MotionEvent::split once all callers handle it
+    //   correctly.
     if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) {
         logDispatchStateLocked();
         LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for "
@@ -4364,44 +4431,32 @@
                                           originalMotionEntry.yPrecision,
                                           originalMotionEntry.xCursorPosition,
                                           originalMotionEntry.yCursorPosition, splitDownTime,
-                                          splitPointerProperties, splitPointerCoords);
+                                          pointerProperties, pointerCoords);
+    if (mTracer) {
+        splitMotionEntry->traceTracker =
+                mTracer->traceDerivedEvent(*splitMotionEntry, *originalMotionEntry.traceTracker);
+    }
 
     return splitMotionEntry;
 }
 
 void InputDispatcher::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
     std::scoped_lock _l(mLock);
+    // Reset key repeating in case a keyboard device was added or removed or something.
+    resetKeyRepeatLocked();
     mLatencyTracker.setInputDevices(args.inputDeviceInfos);
 }
 
-void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    if (debugInboundEventDetails()) {
-        ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args.eventTime);
-    }
-
-    bool needWake = false;
-    { // acquire lock
-        std::scoped_lock _l(mLock);
-
-        std::unique_ptr<ConfigurationChangedEntry> newEntry =
-                std::make_unique<ConfigurationChangedEntry>(args.id, args.eventTime);
-        needWake = enqueueInboundEventLocked(std::move(newEntry));
-    } // release lock
-
-    if (needWake) {
-        mLooper->wake();
-    }
-}
-
 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, "
+             ", deviceId=%d, source=%s, displayId=%s, 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);
+             args.displayId.toString().c_str(), args.policyFlags,
+             KeyEvent::actionToString(args.action), args.flags, KeyEvent::getLabel(args.keyCode),
+             args.scanCode, args.metaState, args.downTime);
     Result<void> keyCheck = validateKeyEvent(args.action);
     if (!keyCheck.ok()) {
         LOG(ERROR) << "invalid key event: " << keyCheck.error();
@@ -4441,6 +4496,10 @@
     { // acquire lock
         mLock.lock();
 
+        if (input_flags::keyboard_repeat_keys() && !mConfig.keyRepeatEnabled) {
+            policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
+        }
+
         if (shouldSendKeyToInputFilterLocked(args)) {
             mLock.unlock();
 
@@ -4477,14 +4536,13 @@
 void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
-              "displayId=%" PRId32 ", policyFlags=0x%x, "
+              "displayId=%s, 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,
+              "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.displayId.toString().c_str(), args.policyFlags,
+              MotionEvent::actionToString(args.action).c_str(), args.actionButton, args.flags,
+              args.metaState, args.buttonState, args.xCursorPosition, args.yCursorPosition,
               args.downTime);
         for (uint32_t i = 0; i < args.getPointerCount(); i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
@@ -4514,7 +4572,8 @@
     if (DEBUG_VERIFY_EVENTS) {
         auto [it, _] =
                 mVerifiersByDisplay.try_emplace(args.displayId,
-                                                StringPrintf("display %" PRId32, args.displayId));
+                                                StringPrintf("display %s",
+                                                             args.displayId.toString().c_str()));
         Result<void> result =
                 it->second.processMovement(args.deviceId, args.source, args.action,
                                            args.getPointerCount(), args.pointerProperties.data(),
@@ -4593,10 +4652,9 @@
         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;
             std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
-            mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime,
-                                          args.deviceId, sources);
+            mLatencyTracker.trackListener(args.id, args.eventTime, args.readTime, args.deviceId,
+                                          sources, args.action, InputEventType::MOTION);
         }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
@@ -4660,6 +4718,7 @@
 }
 
 void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+    // TODO(b/308677868) Remove device reset from the InputListener interface
     if (debugInboundEventDetails()) {
         ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
               args.deviceId);
@@ -4686,7 +4745,7 @@
 void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
     if (debugInboundEventDetails()) {
         ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
-              args.request.enable ? "true" : "false");
+              args.request.isEnable() ? "true" : "false");
     }
 
     bool needWake = false;
@@ -4788,8 +4847,9 @@
             const bool isPointerEvent =
                     isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER);
             // If a pointer event has no displayId specified, inject it to the default display.
-            const int32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
-                    ? ADISPLAY_ID_DEFAULT
+            const ui::LogicalDisplayId displayId =
+                    isPointerEvent && (event->getDisplayId() == ui::LogicalDisplayId::INVALID)
+                    ? ui::LogicalDisplayId::DEFAULT
                     : event->getDisplayId();
             int32_t flags = motionEvent.getFlags();
 
@@ -4810,6 +4870,48 @@
             }
 
             mLock.lock();
+
+            {
+                // Verify all injected streams, whether the injection is coming from apps or from
+                // input filter. Print an error if the stream becomes inconsistent with this event.
+                // An inconsistent injected event sent could cause a crash in the later stages of
+                // dispatching pipeline.
+                auto [it, _] =
+                        mInputFilterVerifiersByDisplay.try_emplace(displayId,
+                                                                   std::string("Injection on ") +
+                                                                           displayId.toString());
+                InputVerifier& verifier = it->second;
+
+                Result<void> result =
+                        verifier.processMovement(resolvedDeviceId, motionEvent.getSource(),
+                                                 motionEvent.getAction(),
+                                                 motionEvent.getPointerCount(),
+                                                 motionEvent.getPointerProperties(),
+                                                 motionEvent.getSamplePointerCoords(), flags);
+                if (!result.ok()) {
+                    logDispatchStateLocked();
+                    LOG(ERROR) << "Inconsistent event: " << motionEvent
+                               << ", reason: " << result.error();
+                    if (policyFlags & POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY) {
+                        mLock.unlock();
+                        return InputEventInjectionResult::FAILED;
+                    }
+                }
+            }
+
+            if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+                // Set the flag anyway if we already have an ongoing motion gesture. That
+                // would allow us to complete the processing of the current stroke.
+                const auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+                if (touchStateIt != mTouchStatesByDisplay.end()) {
+                    const TouchState& touchState = touchStateIt->second;
+                    if (touchState.hasTouchingPointers(resolvedDeviceId) ||
+                        touchState.hasHoveringPointers(resolvedDeviceId)) {
+                        policyFlags |= POLICY_FLAG_PASS_TO_USER;
+                    }
+                }
+            }
+
             const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
             const size_t pointerCount = motionEvent.getPointerCount();
             const std::vector<PointerProperties>
@@ -4859,6 +4961,10 @@
                                                                         pointerCount));
                 transformMotionEntryForInjectionLocked(*nextInjectedEntry,
                                                        motionEvent.getTransform());
+                if (mTracer) {
+                    nextInjectedEntry->traceTracker =
+                            mTracer->traceInboundEvent(*nextInjectedEntry);
+                }
                 injectedEntries.push(std::move(nextInjectedEntry));
             }
             break;
@@ -5030,8 +5136,8 @@
     }
     for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
         entry.pointerCoords[i] =
-                MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
-                                                        entry.pointerCoords[i]);
+                MotionEvent::calculateTransformedCoords(entry.source, entry.flags,
+                                                        transformToDisplay, entry.pointerCoords[i]);
     }
 }
 
@@ -5052,22 +5158,21 @@
 }
 
 const std::vector<sp<WindowInfoHandle>>& InputDispatcher::getWindowHandlesLocked(
-        int32_t displayId) const {
+        ui::LogicalDisplayId displayId) const {
     static const std::vector<sp<WindowInfoHandle>> EMPTY_WINDOW_HANDLES;
     auto it = mWindowHandlesByDisplay.find(displayId);
     return it != mWindowHandlesByDisplay.end() ? it->second : EMPTY_WINDOW_HANDLES;
 }
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
-        const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId) const {
+        const sp<IBinder>& windowHandleToken, std::optional<ui::LogicalDisplayId> displayId) const {
     if (windowHandleToken == nullptr) {
         return nullptr;
     }
 
     if (!displayId) {
         // Look through all displays.
-        for (auto& it : mWindowHandlesByDisplay) {
-            const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+        for (const auto& [_, windowHandles] : mWindowHandlesByDisplay) {
             for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
                 if (windowHandle->getToken() == windowHandleToken) {
                     return windowHandle;
@@ -5088,16 +5193,15 @@
 
 sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
         const sp<WindowInfoHandle>& windowHandle) const {
-    for (auto& it : mWindowHandlesByDisplay) {
-        const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+    for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
         for (const sp<WindowInfoHandle>& handle : windowHandles) {
             if (handle->getId() == windowHandle->getId() &&
                 handle->getToken() == windowHandle->getToken()) {
-                if (windowHandle->getInfo()->displayId != it.first) {
-                    ALOGE("Found window %s in display %" PRId32
-                          ", but it should belong to display %" PRId32,
-                          windowHandle->getName().c_str(), it.first,
-                          windowHandle->getInfo()->displayId);
+                if (windowHandle->getInfo()->displayId != displayId) {
+                    ALOGE("Found window %s in display %s"
+                          ", but it should belong to display %s",
+                          windowHandle->getName().c_str(), displayId.toString().c_str(),
+                          windowHandle->getInfo()->displayId.toString().c_str());
                 }
                 return handle;
             }
@@ -5106,12 +5210,13 @@
     return nullptr;
 }
 
-sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(int displayId) const {
+sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(
+        ui::LogicalDisplayId displayId) const {
     sp<IBinder> focusedToken = mFocusResolver.getFocusedWindowToken(displayId);
     return getWindowHandleLocked(focusedToken, displayId);
 }
 
-ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const {
+ui::Transform InputDispatcher::getTransformLocked(ui::LogicalDisplayId displayId) const {
     auto displayInfoIt = mDisplayInfos.find(displayId);
     return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
                                                 : kIdentityTransform;
@@ -5180,7 +5285,8 @@
 }
 
 void InputDispatcher::updateWindowHandlesForDisplayLocked(
-        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {
+        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles,
+        ui::LogicalDisplayId displayId) {
     if (windowInfoHandles.empty()) {
         // Remove all handles on a display if there are no windows left.
         mWindowHandlesByDisplay.erase(displayId);
@@ -5212,13 +5318,14 @@
         }
 
         if (info->displayId != displayId) {
-            ALOGE("Window %s updated by wrong display %d, should belong to display %d",
-                  handle->getName().c_str(), displayId, info->displayId);
+            ALOGE("Window %s updated by wrong display %s, should belong to display %s",
+                  handle->getName().c_str(), displayId.toString().c_str(),
+                  info->displayId.toString().c_str());
             continue;
         }
 
         if ((oldHandlesById.find(handle->getId()) != oldHandlesById.end()) &&
-                (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
+            (oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
             const sp<WindowInfoHandle>& oldHandle = oldHandlesById.at(handle->getId());
             oldHandle->updateFrom(handle);
             newHandles.push_back(oldHandle);
@@ -5239,7 +5346,8 @@
  * For removed handle, check if need to send a cancel event if already in touch.
  */
 void InputDispatcher::setInputWindowsLocked(
-        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles, int32_t displayId) {
+        const std::vector<sp<WindowInfoHandle>>& windowInfoHandles,
+        ui::LogicalDisplayId displayId) {
     if (DEBUG_FOCUS) {
         std::string windowList;
         for (const sp<WindowInfoHandle>& iwh : windowInfoHandles) {
@@ -5247,6 +5355,7 @@
         }
         LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList;
     }
+    ScopedSyntheticEventTracer traceContext(mTracer);
 
     // Check preconditions for new input windows
     for (const sp<WindowInfoHandle>& window : windowInfoHandles) {
@@ -5286,34 +5395,35 @@
     std::optional<FocusResolver::FocusChanges> changes =
             mFocusResolver.setInputWindows(displayId, windowHandles);
     if (changes) {
-        onFocusChangedLocked(*changes, removedFocusedWindowHandle);
+        onFocusChangedLocked(*changes, traceContext.getTracker(), removedFocusedWindowHandle);
     }
 
-    std::unordered_map<int32_t, TouchState>::iterator stateIt =
-            mTouchStatesByDisplay.find(displayId);
-    if (stateIt != mTouchStatesByDisplay.end()) {
-        TouchState& state = stateIt->second;
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
         for (size_t i = 0; i < state.windows.size();) {
             TouchedWindow& touchedWindow = state.windows[i];
-            if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
-                LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
-                          << " in display %" << displayId;
-                CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                           "touched window was removed");
-                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
-                // Since we are about to drop the touch, cancel the events for the wallpaper as
-                // well.
-                if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
-                    touchedWindow.windowHandle->getInfo()->inputConfig.test(
-                            gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
-                    if (const auto& ww = state.getWallpaperWindow(); ww) {
+            if (getWindowHandleLocked(touchedWindow.windowHandle) != nullptr) {
+                i++;
+                continue;
+            }
+            LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+                      << " in display %" << displayId;
+            CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+                                       "touched window was removed", traceContext.getTracker());
+            synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            // Since we are about to drop the touch, cancel the events for the wallpaper as
+            // well.
+            if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+                touchedWindow.windowHandle->getInfo()->inputConfig.test(
+                        gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+                for (const DeviceId deviceId : touchedWindow.getTouchingDeviceIds()) {
+                    if (const auto& ww = state.getWallpaperWindow(deviceId); ww != nullptr) {
+                        options.deviceId = deviceId;
                         synthesizeCancelationEventsForWindowLocked(ww, options);
                     }
                 }
-                state.windows.erase(state.windows.begin() + i);
-            } else {
-                ++i;
             }
+            state.windows.erase(state.windows.begin() + i);
         }
 
         // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. We
@@ -5327,6 +5437,32 @@
         }
     }
 
+    // Check if the hovering should stop because the window is no longer eligible to receive it
+    // (for example, if the touchable region changed)
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
+        for (TouchedWindow& touchedWindow : state.windows) {
+            std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
+                    [this, displayId, &touchedWindow](const PointerProperties& properties, float x,
+                                                      float y) REQUIRES(mLock) {
+                        const bool isStylus = properties.toolType == ToolType::STYLUS;
+                        const ui::Transform displayTransform = getTransformLocked(displayId);
+                        const bool stillAcceptsTouch =
+                                windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(),
+                                                     displayId, x, y, isStylus, displayTransform);
+                        return !stillAcceptsTouch;
+                    });
+
+            for (DeviceId deviceId : erasedDevices) {
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_HOVER_EVENTS,
+                                           "WindowInfo changed",
+                                           traceContext.getTracker());
+                options.deviceId = deviceId;
+                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            }
+        }
+    }
+
     // Release information for windows that are no longer present.
     // This ensures that unused input channels are released promptly.
     // Otherwise, they might stick around until the window handle is destroyed
@@ -5342,9 +5478,10 @@
 }
 
 void InputDispatcher::setFocusedApplication(
-        int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
+        ui::LogicalDisplayId displayId,
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
     if (DEBUG_FOCUS) {
-        ALOGD("setFocusedApplication displayId=%" PRId32 " %s", displayId,
+        ALOGD("setFocusedApplication displayId=%s %s", displayId.toString().c_str(),
               inputApplicationHandle ? inputApplicationHandle->getName().c_str() : "<nullptr>");
     }
     { // acquire lock
@@ -5357,7 +5494,8 @@
 }
 
 void InputDispatcher::setFocusedApplicationLocked(
-        int32_t displayId, const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
+        ui::LogicalDisplayId displayId,
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) {
     std::shared_ptr<InputApplicationHandle> oldFocusedApplicationHandle =
             getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
 
@@ -5394,12 +5532,13 @@
  * cancel all the unreleased display-unspecified events for the focused window on the old focused
  * display. The display-specified events won't be affected.
  */
-void InputDispatcher::setFocusedDisplay(int32_t displayId) {
+void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) {
     if (DEBUG_FOCUS) {
-        ALOGD("setFocusedDisplay displayId=%" PRId32, displayId);
+        ALOGD("setFocusedDisplay displayId=%s", displayId.toString().c_str());
     }
     { // acquire lock
         std::scoped_lock _l(mLock);
+        ScopedSyntheticEventTracer traceContext(mTracer);
 
         if (mFocusedDisplayId != displayId) {
             sp<IBinder> oldFocusedWindowToken =
@@ -5412,18 +5551,31 @@
                 }
                 CancelationOptions
                         options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                "The display which contains this window no longer has focus.");
-                options.displayId = ADISPLAY_ID_NONE;
+                                "The display which contains this window no longer has focus.",
+                                traceContext.getTracker());
+                options.displayId = ui::LogicalDisplayId::INVALID;
                 synthesizeCancelationEventsForWindowLocked(windowHandle, options);
             }
             mFocusedDisplayId = displayId;
+            // Enqueue a command to run outside the lock to tell the policy that the focused display
+            // changed.
+            auto command = [this]() REQUIRES(mLock) {
+                scoped_unlock unlock(mLock);
+                mPolicy.notifyFocusedDisplayChanged(mFocusedDisplayId);
+            };
+            postCommandLocked(std::move(command));
+
+            // Only a window on the focused display can have Pointer Capture, so disable the active
+            // Pointer Capture session if there is one, since the focused display changed.
+            disablePointerCaptureForcedLocked();
 
             // Find new focused window and validate
             sp<IBinder> newFocusedWindowToken = mFocusResolver.getFocusedWindowToken(displayId);
             sendFocusChangedCommandLocked(oldFocusedWindowToken, newFocusedWindowToken);
 
             if (newFocusedWindowToken == nullptr) {
-                ALOGW("Focused display #%" PRId32 " does not have a focused window.", displayId);
+                ALOGW("Focused display #%s does not have a focused window.",
+                      displayId.toString().c_str());
                 if (mFocusResolver.hasFocusedWindowTokens()) {
                     ALOGE("But another display has a focused window\n%s",
                           mFocusResolver.dumpFocusedWindows().c_str());
@@ -5489,15 +5641,15 @@
 }
 
 bool InputDispatcher::setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid,
-                                     bool hasPermission, int32_t displayId) {
+                                     bool hasPermission, ui::LogicalDisplayId displayId) {
     bool needWake = false;
     {
         std::scoped_lock lock(mLock);
         ALOGD_IF(DEBUG_TOUCH_MODE,
                  "Request to change touch mode to %s (calling pid=%s, uid=%s, "
-                 "hasPermission=%s, target displayId=%d, mTouchModePerDisplay[displayId]=%s)",
+                 "hasPermission=%s, target displayId=%s, mTouchModePerDisplay[displayId]=%s)",
                  toString(inTouchMode), pid.toString().c_str(), uid.toString().c_str(),
-                 toString(hasPermission), displayId,
+                 toString(hasPermission), displayId.toString().c_str(),
                  mTouchModePerDisplay.count(displayId) == 0
                          ? "not set"
                          : std::to_string(mTouchModePerDisplay[displayId]).c_str());
@@ -5555,7 +5707,7 @@
     mMaximumObscuringOpacityForTouch = opacity;
 }
 
-std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
 InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
     for (auto& [displayId, state] : mTouchStatesByDisplay) {
         for (TouchedWindow& w : state.windows) {
@@ -5564,7 +5716,23 @@
             }
         }
     }
-    return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT);
+    return std::make_tuple(nullptr, nullptr, ui::LogicalDisplayId::DEFAULT);
+}
+
+std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
+InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) const {
+    return const_cast<InputDispatcher*>(this)->findTouchStateWindowAndDisplayLocked(token);
+}
+
+bool InputDispatcher::windowHasTouchingPointersLocked(const sp<WindowInfoHandle>& windowHandle,
+                                                      DeviceId deviceId) const {
+    const auto& [touchState, touchedWindow, _] =
+            findTouchStateWindowAndDisplayLocked(windowHandle->getToken());
+    if (touchState == nullptr) {
+        // No touching pointers at all
+        return false;
+    }
+    return touchState->hasTouchingPointers(deviceId);
 }
 
 bool InputDispatcher::transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
@@ -5586,13 +5754,13 @@
             ALOGD("Touch transfer failed because from window is not being touched.");
             return false;
         }
-        std::set<int32_t> deviceIds = touchedWindow->getTouchingDeviceIds();
+        std::set<DeviceId> deviceIds = touchedWindow->getTouchingDeviceIds();
         if (deviceIds.size() != 1) {
             LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
                       << " for window: " << touchedWindow->dump();
             return false;
         }
-        const int32_t deviceId = *deviceIds.begin();
+        const DeviceId deviceId = *deviceIds.begin();
 
         const sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
         const sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
@@ -5633,23 +5801,31 @@
             }
             // Track the pointer id for drag window and generate the drag state.
             const size_t id = pointers.begin()->id;
-            mDragState = std::make_unique<DragState>(toWindowHandle, id);
+            mDragState = std::make_unique<DragState>(toWindowHandle, deviceId, id);
         }
 
         // Synthesize cancel for old window and down for new window.
+        ScopedSyntheticEventTracer traceContext(mTracer);
         std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
         std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
         if (fromConnection != nullptr && toConnection != nullptr) {
             fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
             CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                       "transferring touch from this window to another window");
+                                       "transferring touch from this window to another window",
+                                       traceContext.getTracker());
             synthesizeCancelationEventsForWindowLocked(fromWindowHandle, options, fromConnection);
-            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
-                                                           newTargetFlags);
 
             // Check if the wallpaper window should deliver the corresponding event.
             transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
-                                   *state, deviceId, pointers);
+                                   *state, deviceId, pointers, traceContext.getTracker());
+
+            // Because new window may have a wallpaper window, it will merge input state from it
+            // parent window, after this the firstNewPointerIdx in input state will be reset, then
+            // it will cause new move event be thought inconsistent, so we should synthesize the
+            // down event after it reset.
+            synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
+                                                           newTargetFlags,
+                                                           traceContext.getTracker());
         }
     } // release lock
 
@@ -5663,10 +5839,11 @@
  * Return null if there are no windows touched on that display, or if more than one foreground
  * window is being touched.
  */
-sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t displayId) const {
+sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(
+        ui::LogicalDisplayId displayId) const {
     auto stateIt = mTouchStatesByDisplay.find(displayId);
     if (stateIt == mTouchStatesByDisplay.end()) {
-        ALOGI("No touch state on display %" PRId32, displayId);
+        ALOGI("No touch state on display %s", displayId.toString().c_str());
         return nullptr;
     }
 
@@ -5689,14 +5866,14 @@
 
 // Binder call
 bool InputDispatcher::transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
-                                             int32_t displayId) {
+                                             ui::LogicalDisplayId displayId) {
     sp<IBinder> fromToken;
     { // acquire lock
         std::scoped_lock _l(mLock);
         sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(destChannelToken, displayId);
         if (toWindowHandle == nullptr) {
-            ALOGW("Could not find window associated with token=%p on display %" PRId32,
-                  destChannelToken.get(), displayId);
+            ALOGW("Could not find window associated with token=%p on display %s",
+                  destChannelToken.get(), displayId.toString().c_str());
             return false;
         }
 
@@ -5717,7 +5894,9 @@
         ALOGD("Resetting and dropping all events (%s).", reason);
     }
 
-    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason);
+    ScopedSyntheticEventTracer traceContext(mTracer);
+    CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason,
+                               traceContext.getTracker());
     synthesizeCancelationEventsForAllConnectionsLocked(options);
 
     resetKeyRepeatLocked();
@@ -5745,7 +5924,7 @@
     std::string dump;
 
     dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
-                         toString(mCurrentPointerCaptureRequest.enable));
+                         toString(mCurrentPointerCaptureRequest.isEnable()));
 
     std::string windowName = "None";
     if (mWindowTokenWithPointerCapture) {
@@ -5763,18 +5942,19 @@
     dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
     dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
     dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled));
-    dump += StringPrintf(INDENT "FocusedDisplayId: %" PRId32 "\n", mFocusedDisplayId);
+    dump += StringPrintf(INDENT "FocusedDisplayId: %s\n", mFocusedDisplayId.toString().c_str());
 
     if (!mFocusedApplicationHandlesByDisplay.empty()) {
         dump += StringPrintf(INDENT "FocusedApplications:\n");
         for (auto& it : mFocusedApplicationHandlesByDisplay) {
-            const int32_t displayId = it.first;
+            const ui::LogicalDisplayId displayId = it.first;
             const std::shared_ptr<InputApplicationHandle>& applicationHandle = it.second;
             const std::chrono::duration timeout =
                     applicationHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
-            dump += StringPrintf(INDENT2 "displayId=%" PRId32
-                                         ", name='%s', dispatchingTimeout=%" PRId64 "ms\n",
-                                 displayId, applicationHandle->getName().c_str(), millis(timeout));
+            dump += StringPrintf(INDENT2 "displayId=%s, name='%s', dispatchingTimeout=%" PRId64
+                                         "ms\n",
+                                 displayId.toString().c_str(), applicationHandle->getName().c_str(),
+                                 millis(timeout));
         }
     } else {
         dump += StringPrintf(INDENT "FocusedApplications: <none>\n");
@@ -5787,7 +5967,7 @@
         dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
         for (const auto& [displayId, state] : mTouchStatesByDisplay) {
             std::string touchStateDump = addLinePrefix(state.dump(), INDENT2);
-            dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump;
+            dump += INDENT2 + displayId.toString() + " : " + touchStateDump;
         }
     } else {
         dump += INDENT "TouchStates: <no displays touched>\n";
@@ -5800,7 +5980,7 @@
 
     if (!mWindowHandlesByDisplay.empty()) {
         for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
-            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", displayId);
+            dump += StringPrintf(INDENT "Display: %s\n", displayId.toString().c_str());
             if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
                 const auto& displayInfo = it->second;
                 dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth,
@@ -5826,7 +6006,8 @@
 
     if (!mGlobalMonitorsByDisplay.empty()) {
         for (const auto& [displayId, monitors] : mGlobalMonitorsByDisplay) {
-            dump += StringPrintf(INDENT "Global monitors on display %d:\n", displayId);
+            dump += StringPrintf(INDENT "Global monitors on display %s:\n",
+                                 displayId.toString().c_str());
             dumpMonitors(dump, monitors);
         }
     } else {
@@ -5890,17 +6071,12 @@
                 dump += StringPrintf(INDENT3 "OutboundQueue: length=%zu\n",
                                      connection->outboundQueue.size());
                 dump += dumpQueue(connection->outboundQueue, currentTime);
-
-            } else {
-                dump += INDENT3 "OutboundQueue: <empty>\n";
             }
 
             if (!connection->waitQueue.empty()) {
                 dump += StringPrintf(INDENT3 "WaitQueue: length=%zu\n",
                                      connection->waitQueue.size());
                 dump += dumpQueue(connection->waitQueue, currentTime);
-            } else {
-                dump += INDENT3 "WaitQueue: <empty>\n";
             }
             std::string inputStateDump = streamableToString(connection->inputState);
             if (!inputStateDump.empty()) {
@@ -5915,8 +6091,8 @@
     if (!mTouchModePerDisplay.empty()) {
         dump += INDENT "TouchModePerDisplay:\n";
         for (const auto& [displayId, touchMode] : mTouchModePerDisplay) {
-            dump += StringPrintf(INDENT2 "Display: %" PRId32 " TouchMode: %s\n", displayId,
-                                 std::to_string(touchMode).c_str());
+            dump += StringPrintf(INDENT2 "Display: %s TouchMode: %s\n",
+                                 displayId.toString().c_str(), std::to_string(touchMode).c_str());
         }
     } else {
         dump += INDENT "TouchModePerDisplay: <none>\n";
@@ -5989,9 +6165,8 @@
     return clientChannel;
 }
 
-Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId,
-                                                                          const std::string& name,
-                                                                          gui::Pid pid) {
+Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(
+        ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) {
     std::unique_ptr<InputChannel> serverChannel;
     std::unique_ptr<InputChannel> clientChannel;
     status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
@@ -6002,7 +6177,7 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
 
-        if (displayId < 0) {
+        if (displayId < ui::LogicalDisplayId::DEFAULT) {
             return base::Error(BAD_VALUE) << "Attempted to create input monitor with name " << name
                                           << " without a specified display.";
         }
@@ -6112,12 +6287,13 @@
         return BAD_VALUE;
     }
 
+    ScopedSyntheticEventTracer traceContext(mTracer);
     for (const DeviceId deviceId : deviceIds) {
         TouchState& state = *statePtr;
         TouchedWindow& window = *windowPtr;
         // Send cancel events to all the input channels we're stealing from.
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "input channel stole pointer stream");
+                                   "input channel stole pointer stream", traceContext.getTracker());
         options.deviceId = deviceId;
         options.displayId = displayId;
         std::vector<PointerProperties> pointers = window.getTouchingPointers(deviceId);
@@ -6163,7 +6339,7 @@
             return;
         }
 
-        if (enabled == mCurrentPointerCaptureRequest.enable) {
+        if (enabled == mCurrentPointerCaptureRequest.isEnable()) {
             ALOGW("Ignoring request to %s Pointer Capture: "
                   "window has %s requested pointer capture.",
                   enabled ? "enable" : "disable", enabled ? "already" : "not");
@@ -6179,14 +6355,15 @@
             }
         }
 
-        setPointerCaptureLocked(enabled);
+        setPointerCaptureLocked(enabled ? windowToken : nullptr);
     } // release lock
 
     // Wake the thread to process command entries.
     mLooper->wake();
 }
 
-void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId, bool isEligible) {
+void InputDispatcher::setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                             bool isEligible) {
     { // acquire lock
         std::scoped_lock _l(mLock);
         std::erase(mIneligibleDisplaysForPointerCapture, displayId);
@@ -6263,9 +6440,8 @@
         }
 
         if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
-            const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(dispatchEntry.eventEntry));
             fallbackKeyEntry =
-                    afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+                    afterKeyEventLockedInterruptable(connection, &dispatchEntry, handled);
         }
     } // End critical section: The -LockedInterruptable methods may have released the lock.
 
@@ -6489,8 +6665,17 @@
 }
 
 std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptable(
-        const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-        const KeyEntry& keyEntry, bool handled) {
+        const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry, bool handled) {
+    // The dispatchEntry is currently valid, but it might point to a deleted object after we release
+    // the lock. For simplicity, make copies of the data of interest here and assume that
+    // 'dispatchEntry' is not valid after this section.
+    // Hold a strong reference to the EventEntry to ensure it's valid for the duration of this
+    // function, even if the DispatchEntry gets destroyed and releases its share of the ownership.
+    std::shared_ptr<const EventEntry> eventEntry = dispatchEntry->eventEntry;
+    const bool hasForegroundTarget = dispatchEntry->hasForegroundTarget();
+    const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(eventEntry));
+    // To prevent misuse, ensure dispatchEntry is no longer valid.
+    dispatchEntry = nullptr;
     if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
         if (!handled) {
             // Report the key as unhandled, since the fallback was not handled.
@@ -6507,7 +6692,7 @@
         connection->inputState.removeFallbackKey(originalKeyCode);
     }
 
-    if (handled || !dispatchEntry.hasForegroundTarget()) {
+    if (handled || !hasForegroundTarget) {
         // 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.
@@ -6541,7 +6726,8 @@
                     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");
+                                               "canceling previously dispatched fallback key",
+                                               keyEntry.traceTracker);
                     options.keyCode = *fallbackKeyCode;
                     synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
                 }
@@ -6623,7 +6809,8 @@
             const auto windowHandle = getWindowHandleLocked(connection->getToken());
             if (windowHandle != nullptr) {
                 CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
-                                           "canceling fallback, policy no longer desires it");
+                                           "canceling fallback, policy no longer desires it",
+                                           keyEntry.traceTracker);
                 options.keyCode = *fallbackKeyCode;
                 synthesizeCancelationEventsForWindowLocked(windowHandle, options, connection);
             }
@@ -6659,6 +6846,10 @@
                                                *fallbackKeyCode, event.getScanCode(),
                                                event.getMetaState(), event.getRepeatCount(),
                                                event.getDownTime());
+            if (mTracer) {
+                newEntry->traceTracker =
+                        mTracer->traceDerivedEvent(*newEntry, *keyEntry.traceTracker);
+            }
             if (DEBUG_OUTBOUND_EVENT_DETAILS) {
                 ALOGD("Unhandled key event: Dispatching fallback key.  "
                       "originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
@@ -6756,17 +6947,22 @@
     { // acquire lock
         std::scoped_lock _l(mLock);
         std::optional<FocusResolver::FocusChanges> changes =
-                mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));
+                mFocusResolver.setFocusedWindow(request,
+                                                getWindowHandlesLocked(
+                                                        ui::LogicalDisplayId{request.displayId}));
+        ScopedSyntheticEventTracer traceContext(mTracer);
         if (changes) {
-            onFocusChangedLocked(*changes);
+            onFocusChangedLocked(*changes, traceContext.getTracker());
         }
     } // release lock
     // Wake up poll loop since it may need to make new input dispatching choices.
     mLooper->wake();
 }
 
-void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& changes,
-                                           const sp<WindowInfoHandle> removedFocusedWindowHandle) {
+void InputDispatcher::onFocusChangedLocked(
+        const FocusResolver::FocusChanges& changes,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
+        const sp<WindowInfoHandle> removedFocusedWindowHandle) {
     if (changes.oldFocus) {
         const auto resolvedWindow = removedFocusedWindowHandle != nullptr
                 ? removedFocusedWindowHandle
@@ -6775,7 +6971,7 @@
             LOG(FATAL) << __func__ << ": Previously focused token did not have a window";
         }
         CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
-                                   "focus left window");
+                                   "focus left window", traceTracker);
         synthesizeCancelationEventsForWindowLocked(resolvedWindow, options);
         enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
     }
@@ -6784,30 +6980,30 @@
         enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
     }
 
-    // If a window has pointer capture, then it must have focus. We need to ensure that this
-    // contract is upheld when pointer capture is being disabled due to a loss of window focus.
-    // If the window loses focus before it loses pointer capture, then the window can be in a state
-    // where it has pointer capture but not focus, violating the contract. Therefore we must
-    // dispatch the pointer capture event before the focus event. Since focus events are added to
-    // the front of the queue (above), we add the pointer capture event to the front of the queue
-    // after the focus events are added. This ensures the pointer capture event ends up at the
-    // front.
-    disablePointerCaptureForcedLocked();
-
     if (mFocusedDisplayId == changes.displayId) {
+        // If a window has pointer capture, then it must have focus and must be on the top-focused
+        // display. We need to ensure that this contract is upheld when pointer capture is being
+        // disabled due to a loss of window focus. If the window loses focus before it loses pointer
+        // capture, then the window can be in a state where it has pointer capture but not focus,
+        // violating the contract. Therefore we must dispatch the pointer capture event before the
+        // focus event. Since focus events are added to the front of the queue (above), we add the
+        // pointer capture event to the front of the queue after the focus events are added. This
+        // ensures the pointer capture event ends up at the front.
+        disablePointerCaptureForcedLocked();
+
         sendFocusChangedCommandLocked(changes.oldFocus, changes.newFocus);
     }
 }
 
 void InputDispatcher::disablePointerCaptureForcedLocked() {
-    if (!mCurrentPointerCaptureRequest.enable && !mWindowTokenWithPointerCapture) {
+    if (!mCurrentPointerCaptureRequest.isEnable() && !mWindowTokenWithPointerCapture) {
         return;
     }
 
     ALOGD_IF(DEBUG_FOCUS, "Disabling Pointer Capture because the window lost focus.");
 
-    if (mCurrentPointerCaptureRequest.enable) {
-        setPointerCaptureLocked(false);
+    if (mCurrentPointerCaptureRequest.isEnable()) {
+        setPointerCaptureLocked(nullptr);
     }
 
     if (!mWindowTokenWithPointerCapture) {
@@ -6827,8 +7023,8 @@
     mInboundQueue.push_front(std::move(entry));
 }
 
-void InputDispatcher::setPointerCaptureLocked(bool enable) {
-    mCurrentPointerCaptureRequest.enable = enable;
+void InputDispatcher::setPointerCaptureLocked(const sp<IBinder>& windowToken) {
+    mCurrentPointerCaptureRequest.window = windowToken;
     mCurrentPointerCaptureRequest.seq++;
     auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
         scoped_unlock unlock(mLock);
@@ -6837,7 +7033,7 @@
     postCommandLocked(std::move(command));
 }
 
-void InputDispatcher::displayRemoved(int32_t displayId) {
+void InputDispatcher::displayRemoved(ui::LogicalDisplayId displayId) {
     { // acquire lock
         std::scoped_lock _l(mLock);
         // Set an empty list to remove all handles from the specific display.
@@ -6851,6 +7047,7 @@
         // Remove the associated touch mode state.
         mTouchModePerDisplay.erase(displayId);
         mVerifiersByDisplay.erase(displayId);
+        mInputFilterVerifiersByDisplay.erase(displayId);
     } // release lock
 
     // Wake up poll loop since it may need to make new input dispatching choices.
@@ -6869,10 +7066,17 @@
     };
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
-    std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
+    std::unordered_map<ui::LogicalDisplayId, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
     for (const auto& info : update.windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
+        if (input_flags::split_all_touches()) {
+            handlesPerDisplay[info.displayId]
+                    .back()
+                    ->editInfo()
+                    ->setInputConfig(android::gui::WindowInfo::InputConfig::PREVENT_SPLITTING,
+                                     false);
+        }
     }
 
     { // acquire lock
@@ -6911,10 +7115,10 @@
                  WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED) &&
          isWindowObscuredLocked(windowHandle))) {
         ALOGW("Dropping %s event targeting %s as requested by the input configuration {%s} on "
-              "display %" PRId32 ".",
+              "display %s.",
               ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
               windowHandle->getInfo()->inputConfig.string().c_str(),
-              windowHandle->getInfo()->displayId);
+              windowHandle->getInfo()->displayId.toString().c_str());
         return true;
     }
     return false;
@@ -6928,9 +7132,10 @@
 void InputDispatcher::cancelCurrentTouch() {
     {
         std::scoped_lock _l(mLock);
+        ScopedSyntheticEventTracer traceContext(mTracer);
         ALOGD("Canceling all ongoing pointer gestures on all displays.");
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "cancel current touch");
+                                   "cancel current touch", traceContext.getTracker());
         synthesizeCancelationEventsForAllConnectionsLocked(options);
 
         mTouchStatesByDisplay.clear();
@@ -6947,9 +7152,11 @@
 void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                                          const sp<WindowInfoHandle>& oldWindowHandle,
                                          const sp<WindowInfoHandle>& newWindowHandle,
-                                         TouchState& state, int32_t deviceId,
-                                         const PointerProperties& pointerProperties,
+                                         TouchState& state, const MotionEntry& entry,
                                          std::vector<InputTarget>& targets) const {
+    LOG_IF(FATAL, entry.getPointerCount() != 1) << "Entry not eligible for slip: " << entry;
+    const DeviceId deviceId = entry.deviceId;
+    const PointerProperties& pointerProperties = entry.pointerProperties[0];
     std::vector<PointerProperties> pointers{pointerProperties};
     const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6957,7 +7164,7 @@
             newWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
@@ -6976,16 +7183,16 @@
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
                                         InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
-                                deviceId, pointers);
+                                deviceId, pointers, entry.eventTime);
     }
 }
 
-void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
-                                             ftl::Flags<InputTarget::Flags> newTargetFlags,
-                                             const sp<WindowInfoHandle> fromWindowHandle,
-                                             const sp<WindowInfoHandle> toWindowHandle,
-                                             TouchState& state, int32_t deviceId,
-                                             const std::vector<PointerProperties>& pointers) {
+void InputDispatcher::transferWallpaperTouch(
+        ftl::Flags<InputTarget::Flags> oldTargetFlags,
+        ftl::Flags<InputTarget::Flags> newTargetFlags, const sp<WindowInfoHandle> fromWindowHandle,
+        const sp<WindowInfoHandle> toWindowHandle, TouchState& state, DeviceId deviceId,
+        const std::vector<PointerProperties>& pointers,
+        const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -6994,7 +7201,7 @@
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
 
     const sp<WindowInfoHandle> oldWallpaper =
-            oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+            oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
             newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
@@ -7003,7 +7210,7 @@
 
     if (oldWallpaper != nullptr) {
         CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
-                                   "transferring touch focus to another window");
+                                   "transferring touch focus to another window", traceTracker);
         state.removeWindowByToken(oldWallpaper->getToken());
         synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
     }
@@ -7023,7 +7230,7 @@
                     getConnectionLocked(toWindowHandle->getToken());
             toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
             synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
-                                                           wallpaperFlags);
+                                                           wallpaperFlags, traceTracker);
         }
     }
 }
@@ -7050,15 +7257,18 @@
 }
 
 void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                                std::chrono::nanoseconds delay) {
+                                                std::chrono::nanoseconds delay,
+                                                bool keyRepeatEnabled) {
     std::scoped_lock _l(mLock);
 
     mConfig.keyRepeatTimeout = timeout.count();
     mConfig.keyRepeatDelay = delay.count();
+    mConfig.keyRepeatEnabled = keyRepeatEnabled;
 }
 
-bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token, int32_t displayId,
-                                        DeviceId deviceId, int32_t pointerId) {
+bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token,
+                                        ui::LogicalDisplayId displayId, DeviceId deviceId,
+                                        int32_t pointerId) {
     std::scoped_lock _l(mLock);
     auto touchStateIt = mTouchStatesByDisplay.find(displayId);
     if (touchStateIt == mTouchStatesByDisplay.end()) {
@@ -7074,4 +7284,11 @@
     return false;
 }
 
+void InputDispatcher::setInputMethodConnectionIsActive(bool isActive) {
+    std::scoped_lock _l(mLock);
+    if (mTracer) {
+        mTracer->setInputMethodConnectionIsActive(isActive);
+    }
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 269bfdd..1904058 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -97,7 +97,6 @@
     status_t stop() 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;
@@ -114,35 +113,37 @@
     std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) override;
 
     void setFocusedApplication(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override;
-    void setFocusedDisplay(int32_t displayId) override;
+    void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
     void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) override;
     void setInputDispatchMode(bool enabled, bool frozen) override;
     void setInputFilterEnabled(bool enabled) override;
     bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
-                        int32_t displayId) override;
+                        ui::LogicalDisplayId displayId) override;
     void setMaximumObscuringOpacityForTouch(float opacity) override;
 
     bool transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
                               bool isDragDrop = false) override;
-    bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken, int32_t displayId) override;
+    bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
+                                ui::LogicalDisplayId displayId) override;
 
     base::Result<std::unique_ptr<InputChannel>> createInputChannel(
             const std::string& name) override;
     void setFocusedWindow(const android::gui::FocusRequest&) override;
-    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
+    base::Result<std::unique_ptr<InputChannel>> createInputMonitor(ui::LogicalDisplayId displayId,
                                                                    const std::string& name,
                                                                    gui::Pid pid) override;
     status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
     status_t pilferPointers(const sp<IBinder>& token) override;
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
     bool flushSensor(int deviceId, InputDeviceSensorType sensorType) override;
-    void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) override;
+    void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                bool isEligible) override;
 
     std::array<uint8_t, 32> sign(const VerifiedInputEvent& event) const;
 
-    void displayRemoved(int32_t displayId) override;
+    void displayRemoved(ui::LogicalDisplayId displayId) override;
 
     // Public because it's also used by tests to simulate the WindowInfosListener callback
     void onWindowInfosChanged(const gui::WindowInfosUpdate&);
@@ -152,11 +153,13 @@
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
-    void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                   std::chrono::nanoseconds delay) override;
+    void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout, std::chrono::nanoseconds delay,
+                                   bool keyRepeatEnabled) override;
 
-    bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
-                           int32_t pointerId) override;
+    bool isPointerInWindow(const sp<IBinder>& token, ui::LogicalDisplayId displayId,
+                           DeviceId deviceId, int32_t pointerId) override;
+
+    void setInputMethodConnectionIsActive(bool isActive) override;
 
 private:
     enum class DropReason {
@@ -247,17 +250,18 @@
     std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
 
     sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
-            int32_t displayId, float x, float y, bool isStylus = false,
+            ui::LogicalDisplayId displayId, float x, float y, bool isStylus = false,
             bool ignoreDragWindow = false) const REQUIRES(mLock);
     std::vector<InputTarget> findOutsideTargetsLocked(
-            int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+            ui::LogicalDisplayId displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
             int32_t pointerId) const REQUIRES(mLock);
 
     std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
-            int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
+            ui::LogicalDisplayId displayId, float x, float y, bool isStylus,
+            DeviceId deviceId) const REQUIRES(mLock);
 
-    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
-            REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
     std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
             REQUIRES(mLock);
@@ -281,7 +285,8 @@
     std::optional<gui::Pid> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
     // Input channels that will receive a copy of all input events sent to the provided display.
-    std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
+    std::unordered_map<ui::LogicalDisplayId, std::vector<Monitor>> mGlobalMonitorsByDisplay
+            GUARDED_BY(mLock);
 
     const HmacKeyManager mHmacKeyManager;
     const std::array<uint8_t, 32> getSignature(const MotionEntry& motionEntry,
@@ -296,7 +301,9 @@
     void transformMotionEntryForInjectionLocked(MotionEntry&,
                                                 const ui::Transform& injectedTransform) const
             REQUIRES(mLock);
-
+    // Per-display correction of injected events
+    std::map<android::ui::LogicalDisplayId, InputVerifier> mInputFilterVerifiersByDisplay
+            GUARDED_BY(mLock);
     std::condition_variable mInjectionSyncFinished;
     void incrementPendingForegroundDispatches(const EventEntry& entry);
     void decrementPendingForegroundDispatches(const EventEntry& entry);
@@ -340,7 +347,8 @@
     // This map is not really needed, but it helps a lot with debugging (dumpsys input).
     // In the java layer, touch mode states are spread across multiple DisplayContent objects,
     // making harder to snapshot and retrieve them.
-    std::map<int32_t /*displayId*/, bool /*inTouchMode*/> mTouchModePerDisplay GUARDED_BY(mLock);
+    std::map<ui::LogicalDisplayId /*displayId*/, bool /*inTouchMode*/> mTouchModePerDisplay
+            GUARDED_BY(mLock);
 
     class DispatcherWindowListener : public gui::WindowInfosListener {
     public:
@@ -352,25 +360,26 @@
     };
     sp<gui::WindowInfosListener> mWindowInfoListener;
 
-    std::unordered_map<int32_t /*displayId*/, std::vector<sp<android::gui::WindowInfoHandle>>>
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/,
+                       std::vector<sp<android::gui::WindowInfoHandle>>>
             mWindowHandlesByDisplay GUARDED_BY(mLock);
-    std::unordered_map<int32_t /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
             GUARDED_BY(mLock);
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
-            int32_t displayId) REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) REQUIRES(mLock);
     // Get a reference to window handles by display, return an empty vector if not found.
     const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
-            int32_t displayId) const REQUIRES(mLock);
-    ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
+    ui::Transform getTransformLocked(ui::LogicalDisplayId displayId) const REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
-            const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId = {}) const
-            REQUIRES(mLock);
+            const sp<IBinder>& windowHandleToken,
+            std::optional<ui::LogicalDisplayId> displayId = {}) const REQUIRES(mLock);
     sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
-    sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(int displayId) const
-            REQUIRES(mLock);
+    sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(
+            ui::LogicalDisplayId displayId) const REQUIRES(mLock);
     bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window,
                                       const MotionEntry& motionEntry) const REQUIRES(mLock);
 
@@ -385,20 +394,21 @@
      */
     void updateWindowHandlesForDisplayLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
-            int32_t displayId) REQUIRES(mLock);
+            ui::LogicalDisplayId displayId) REQUIRES(mLock);
 
-    std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, TouchState> mTouchStatesByDisplay
+            GUARDED_BY(mLock);
     std::unique_ptr<DragState> mDragState GUARDED_BY(mLock);
 
     void setFocusedApplicationLocked(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) REQUIRES(mLock);
     // Focused applications.
-    std::unordered_map<int32_t, std::shared_ptr<InputApplicationHandle>>
+    std::unordered_map<ui::LogicalDisplayId /*displayId*/, std::shared_ptr<InputApplicationHandle>>
             mFocusedApplicationHandlesByDisplay GUARDED_BY(mLock);
 
     // Top focused display.
-    int32_t mFocusedDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mFocusedDisplayId GUARDED_BY(mLock);
 
     // Keeps track of the focused window per display and determines focus changes.
     FocusResolver mFocusResolver GUARDED_BY(mLock);
@@ -415,13 +425,15 @@
 
     // Displays that are ineligible for pointer capture.
     // TODO(b/214621487): Remove or move to a display flag.
-    std::vector<int32_t> mIneligibleDisplaysForPointerCapture GUARDED_BY(mLock);
+    std::vector<ui::LogicalDisplayId /*displayId*/> mIneligibleDisplaysForPointerCapture
+            GUARDED_BY(mLock);
 
     // Disable Pointer Capture as a result of loss of window focus.
     void disablePointerCaptureForcedLocked() REQUIRES(mLock);
 
     // Set the Pointer Capture state in the Policy.
-    void setPointerCaptureLocked(bool enable) REQUIRES(mLock);
+    // The window is not nullptr for requests to enable, otherwise it is nullptr.
+    void setPointerCaptureLocked(const sp<IBinder>& window) REQUIRES(mLock);
 
     // Dispatcher state at time of last ANR.
     std::string mLastAnrState GUARDED_BY(mLock);
@@ -434,8 +446,6 @@
             REQUIRES(mLock);
 
     // Dispatch inbound events.
-    bool dispatchConfigurationChangedLocked(nsecs_t currentTime,
-                                            const ConfigurationChangedEntry& entry) REQUIRES(mLock);
     bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry)
             REQUIRES(mLock);
     bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
@@ -489,7 +499,7 @@
     /**
      * The displayId that the focused application is associated with.
      */
-    int32_t mAwaitedApplicationDisplayId GUARDED_BY(mLock);
+    ui::LogicalDisplayId mAwaitedApplicationDisplayId GUARDED_BY(mLock);
     void processNoFocusedWindowAnrLocked() REQUIRES(mLock);
 
     /**
@@ -523,13 +533,12 @@
     // shade is pulled down while we are counting down the timeout).
     void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
 
-    int32_t getTargetDisplayId(const EventEntry& entry);
-    sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
-            nsecs_t currentTime, const EventEntry& entry, nsecs_t& nextWakeupTime,
-            android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
-    std::vector<InputTarget> findTouchedWindowTargetsLocked(
-            nsecs_t currentTime, const MotionEntry& entry,
-            android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
+    ui::LogicalDisplayId getTargetDisplayId(const EventEntry& entry);
+    base::Result<sp<android::gui::WindowInfoHandle>, android::os::InputEventInjectionResult>
+    findFocusedWindowTargetLocked(nsecs_t currentTime, const EventEntry& entry,
+                                  nsecs_t& nextWakeupTime) REQUIRES(mLock);
+    base::Result<std::vector<InputTarget>, android::os::InputEventInjectionResult>
+    findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry& entry) REQUIRES(mLock);
     std::vector<Monitor> selectResponsiveMonitorsLocked(
             const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
@@ -548,13 +557,13 @@
                                       std::bitset<MAX_POINTER_ID + 1> pointerIds,
                                       std::optional<nsecs_t> firstDownTimeInTarget,
                                       std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
-    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
-            REQUIRES(mLock);
+    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
+                                          ui::LogicalDisplayId displayId) REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
     // Enqueue a drag event if needed, and update the touch state.
     // Uses findTouchedWindowTargetsLocked to make the decision
     void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock);
-    void finishDragAndDrop(int32_t displayId, float x, float y) REQUIRES(mLock);
+    void finishDragAndDrop(ui::LogicalDisplayId displayId, float x, float y) REQUIRES(mLock);
 
     struct TouchOcclusionInfo {
         bool hasBlockingOcclusion;
@@ -565,11 +574,11 @@
     };
 
     TouchOcclusionInfo computeTouchOcclusionInfoLocked(
-            const sp<android::gui::WindowInfoHandle>& windowHandle, int32_t x, int32_t y) const
+            const sp<android::gui::WindowInfoHandle>& windowHandle, float x, float y) const
             REQUIRES(mLock);
     bool isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const REQUIRES(mLock);
     bool isWindowObscuredAtPointLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                       int32_t x, int32_t y) const REQUIRES(mLock);
+                                       float x, float y) const REQUIRES(mLock);
     bool isWindowObscuredLocked(const sp<android::gui::WindowInfoHandle>& windowHandle) const
             REQUIRES(mLock);
     std::string dumpWindowForTouchOcclusion(const android::gui::WindowInfo* info,
@@ -628,7 +637,8 @@
 
     void synthesizePointerDownEventsForConnectionLocked(
             const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
-            ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock);
+            ftl::Flags<InputTarget::Flags> targetFlags,
+            const std::unique_ptr<trace::EventTrackerInterface>& traceTracker) REQUIRES(mLock);
 
     // Splitting motion events across windows. When splitting motion event for a target,
     // splitDownTime refers to the time of first 'down' event on that particular target
@@ -657,6 +667,7 @@
     void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
                                                 const KeyEntry& entry) REQUIRES(mLock);
     void onFocusChangedLocked(const FocusResolver::FocusChanges& changes,
+                              const std::unique_ptr<trace::EventTrackerInterface>& traceTracker,
                               const sp<gui::WindowInfoHandle> removedFocusedWindowHandle = nullptr)
             REQUIRES(mLock);
     void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
@@ -670,16 +681,21 @@
                                   const std::string& reason) REQUIRES(mLock);
     void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
             REQUIRES(mLock);
-    std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
+    std::map<ui::LogicalDisplayId, InputVerifier> mVerifiersByDisplay;
     // Returns a fallback KeyEntry that should be sent to the connection, if required.
     std::unique_ptr<const KeyEntry> afterKeyEventLockedInterruptable(
-            const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
-            const KeyEntry& keyEntry, bool handled) REQUIRES(mLock);
+            const std::shared_ptr<Connection>& connection, DispatchEntry* dispatchEntry,
+            bool handled) REQUIRES(mLock);
 
     // Find touched state and touched window by token.
-    std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+    std::tuple<TouchState*, TouchedWindow*, ui::LogicalDisplayId>
     findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
 
+    std::tuple<const TouchState*, const TouchedWindow*, ui::LogicalDisplayId>
+    findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) const REQUIRES(mLock);
+    bool windowHasTouchingPointersLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                         DeviceId deviceId) const REQUIRES(mLock);
+
     // Statistics gathering.
     LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
     LatencyTracker mLatencyTracker GUARDED_BY(mLock);
@@ -693,18 +709,32 @@
 
     sp<InputReporterInterface> mReporter;
 
+    /**
+     * Slip the wallpaper touch if necessary.
+     *
+     * @param targetFlags the target flags
+     * @param oldWindowHandle the old window that the touch slipped out of
+     * @param newWindowHandle the new window that the touch is slipping into
+     * @param state the current touch state. This will be updated if necessary to reflect the new
+     *        windows that are receiving touch.
+     * @param deviceId the device id of the current motion being processed
+     * @param pointerProperties the pointer properties of the current motion being processed
+     * @param targets the current targets to add the walpaper ones to
+     * @param eventTime the new downTime for the wallpaper target
+     */
     void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
                             const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
                             const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                            TouchState& state, int32_t deviceId,
-                            const PointerProperties& pointerProperties,
+                            TouchState& state, const MotionEntry& entry,
                             std::vector<InputTarget>& targets) const REQUIRES(mLock);
     void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
                                 ftl::Flags<InputTarget::Flags> newTargetFlags,
                                 const sp<android::gui::WindowInfoHandle> fromWindowHandle,
                                 const sp<android::gui::WindowInfoHandle> toWindowHandle,
-                                TouchState& state, int32_t deviceId,
-                                const std::vector<PointerProperties>& pointers) REQUIRES(mLock);
+                                TouchState& state, DeviceId deviceId,
+                                const std::vector<PointerProperties>& pointers,
+                                const std::unique_ptr<trace::EventTrackerInterface>& traceTracker)
+            REQUIRES(mLock);
 
     sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
index a7c6d16..6881964 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.cpp
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -66,15 +66,16 @@
     return !operator==(rhs);
 }
 
-InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime,
-                                       uint16_t vendorId, uint16_t productId,
-                                       std::set<InputDeviceUsageSource> sources)
-      : isDown(isDown),
-        eventTime(eventTime),
+InputEventTimeline::InputEventTimeline(nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
+                                       uint16_t productId,
+                                       const std::set<InputDeviceUsageSource>& sources,
+                                       InputEventActionType inputEventActionType)
+      : eventTime(eventTime),
         readTime(readTime),
         vendorId(vendorId),
         productId(productId),
-        sources(sources) {}
+        sources(sources),
+        inputEventActionType(inputEventActionType) {}
 
 bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
     if (connectionTimelines.size() != rhs.connectionTimelines.size()) {
@@ -89,8 +90,9 @@
             return false;
         }
     }
-    return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime &&
-            vendorId == rhs.vendorId && productId == rhs.productId && sources == rhs.sources;
+    return eventTime == rhs.eventTime && readTime == rhs.readTime && vendorId == rhs.vendorId &&
+            productId == rhs.productId && sources == rhs.sources &&
+            inputEventActionType == rhs.inputEventActionType;
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
index e9deb2d..951fcc8 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.h
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -74,15 +74,38 @@
     bool mHasGraphicsTimeline = false;
 };
 
+enum class InputEventActionType : int32_t {
+    UNKNOWN_INPUT_EVENT = 0,
+    MOTION_ACTION_DOWN = 1,
+    // Motion events for ACTION_MOVE (characterizes scrolling motion)
+    MOTION_ACTION_MOVE = 2,
+    // Motion events for ACTION_UP (when the pointer first goes up)
+    MOTION_ACTION_UP = 3,
+    // Motion events for ACTION_HOVER_MOVE (pointer position on screen changes but pointer is not
+    // down)
+    MOTION_ACTION_HOVER_MOVE = 4,
+    // Motion events for ACTION_SCROLL (moving the mouse wheel)
+    MOTION_ACTION_SCROLL = 5,
+    // Key events for both ACTION_DOWN and ACTION_UP (key press and key release)
+    KEY = 6,
+
+    ftl_first = UNKNOWN_INPUT_EVENT,
+    ftl_last = KEY,
+    // Used by latency fuzzer
+    kMaxValue = ftl_last
+
+};
+
 struct InputEventTimeline {
-    InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
-                       uint16_t productId, std::set<InputDeviceUsageSource> sources);
-    const bool isDown; // True if this is an ACTION_DOWN event
+    InputEventTimeline(nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId, uint16_t productId,
+                       const std::set<InputDeviceUsageSource>& sources,
+                       InputEventActionType inputEventActionType);
     const nsecs_t eventTime;
     const nsecs_t readTime;
     const uint16_t vendorId;
     const uint16_t productId;
     const std::set<InputDeviceUsageSource> sources;
+    const InputEventActionType inputEventActionType;
 
     struct IBinderHash {
         std::size_t operator()(const sp<IBinder>& b) const {
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 1fec9b7..9b5a79b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -28,7 +28,8 @@
 
 InputState::~InputState() {}
 
-bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const {
+bool InputState::isHovering(DeviceId deviceId, uint32_t source,
+                            ui::LogicalDisplayId displayId) const {
     for (const MotionMemento& memento : mMotionMementos) {
         if (memento.deviceId == deviceId && memento.source == source &&
             memento.displayId == displayId && memento.hovering) {
@@ -95,12 +96,14 @@
         return true;
     }
 
-    if (!mMotionMementos.empty()) {
-        const MotionMemento& lastMemento = mMotionMementos.back();
-        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
-            !isStylusEvent(entry.source, entry.pointerProperties)) {
-            // We already have a stylus stream, and the new event is not from stylus.
-            return false;
+    if (!input_flags::enable_multi_device_same_window_stream()) {
+        if (!mMotionMementos.empty()) {
+            const MotionMemento& lastMemento = mMotionMementos.back();
+            if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+                !isStylusEvent(entry.source, entry.pointerProperties)) {
+                // We already have a stylus stream, and the new event is not from stylus.
+                return false;
+            }
         }
     }
 
@@ -336,33 +339,34 @@
         // it's unlikely that those two streams would be consistent with each other. Therefore,
         // cancel the previous gesture if the display id changes.
         if (motionEntry.displayId != lastMemento.displayId) {
-            LOG(INFO) << "Canceling stream: last displayId was "
-                      << inputEventSourceToString(lastMemento.displayId) << " and new event is "
-                      << motionEntry;
+            LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId
+                      << " and new event is " << motionEntry;
             return true;
         }
 
         return false;
     }
 
-    if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
-        // A stylus is already active.
-        if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
-            actionMasked == AMOTION_EVENT_ACTION_DOWN) {
-            // If this new event is from a different device, then cancel the old
-            // stylus and allow the new stylus to take over, but only if it's going down.
-            // Otherwise, they will start to race each other.
-            return true;
+    if (!input_flags::enable_multi_device_same_window_stream()) {
+        if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+            // A stylus is already active.
+            if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+                actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+                // If this new event is from a different device, then cancel the old
+                // stylus and allow the new stylus to take over, but only if it's going down.
+                // Otherwise, they will start to race each other.
+                return true;
+            }
+
+            // Keep the current stylus gesture.
+            return false;
         }
 
-        // Keep the current stylus gesture.
-        return false;
-    }
-
-    // Cancel the current gesture if this is a start of a new gesture from a new device.
-    if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
-        actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
-        return true;
+        // Cancel the current gesture if this is a start of a new gesture from a new device.
+        if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+            actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+            return true;
+        }
     }
     // By default, don't cancel any events.
     return false;
@@ -495,67 +499,53 @@
         nsecs_t currentTime) {
     std::vector<std::unique_ptr<MotionEntry>> events;
     std::vector<uint32_t> canceledPointerIndices;
-    std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
-    std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
+
     for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
         uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
-        pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
-        pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
         if (pointerIds.test(pointerId)) {
             canceledPointerIndices.push_back(pointerIdx);
         }
     }
 
     if (canceledPointerIndices.size() == memento.getPointerCount()) {
-        const int32_t action =
-                memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
-        int32_t flags = memento.flags;
-        if (action == AMOTION_EVENT_ACTION_CANCEL) {
-            flags |= AMOTION_EVENT_FLAG_CANCELED;
+        // We are cancelling all pointers.
+        events.emplace_back(createCancelEntryForMemento(memento, currentTime));
+        return events;
+    }
+
+    // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
+    // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
+    // previously canceled pointers from PointerProperties and PointerCoords, and update
+    // pointerCount appropriately. For convenience, sort the canceled pointer indices in
+    // descending order so that we can just slide the remaining pointers to the beginning of
+    // the array when a pointer is canceled.
+    std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
+              std::greater<uint32_t>());
+
+    std::vector<PointerProperties> pointerProperties = memento.pointerProperties;
+    std::vector<PointerCoords> pointerCoords = memento.pointerCoords;
+    for (const uint32_t pointerIdx : canceledPointerIndices) {
+        if (pointerProperties.size() <= 1) {
+            LOG(FATAL) << "Unexpected code path for canceling all pointers!";
         }
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
         events.push_back(
                 std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
                                               currentTime, memento.deviceId, memento.source,
                                               memento.displayId, memento.policyFlags, action,
-                                              /*actionButton=*/0, flags, AMETA_NONE,
-                                              /*buttonState=*/0, MotionClassification::NONE,
+                                              /*actionButton=*/0,
+                                              memento.flags | AMOTION_EVENT_FLAG_CANCELED,
+                                              AMETA_NONE, /*buttonState=*/0,
+                                              MotionClassification::NONE,
                                               AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
                                               memento.yPrecision, memento.xCursorPosition,
                                               memento.yCursorPosition, memento.downTime,
-                                              memento.pointerProperties, memento.pointerCoords));
-    } else {
-        // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
-        // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
-        // previously canceled pointers from PointerProperties and PointerCoords, and update
-        // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
-        // can just slide the remaining pointers to the beginning of the array when a pointer is
-        // canceled.
-        std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
-                  std::greater<uint32_t>());
+                                              pointerProperties, pointerCoords));
 
-        uint32_t pointerCount = memento.getPointerCount();
-        for (const uint32_t pointerIdx : canceledPointerIndices) {
-            const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
-                                                     : AMOTION_EVENT_ACTION_POINTER_UP |
-                            (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-            events.push_back(
-                    std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
-                                                  currentTime, memento.deviceId, memento.source,
-                                                  memento.displayId, memento.policyFlags, action,
-                                                  /*actionButton=*/0,
-                                                  memento.flags | AMOTION_EVENT_FLAG_CANCELED,
-                                                  AMETA_NONE, /*buttonState=*/0,
-                                                  MotionClassification::NONE,
-                                                  AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
-                                                  memento.yPrecision, memento.xCursorPosition,
-                                                  memento.yCursorPosition, memento.downTime,
-                                                  pointerProperties, pointerCoords));
-
-            // Cleanup pointer information
-            pointerProperties.erase(pointerProperties.begin() + pointerIdx);
-            pointerCoords.erase(pointerCoords.begin() + pointerIdx);
-            pointerCount--;
-        }
+        // Cleanup pointer information
+        pointerProperties.erase(pointerProperties.begin() + pointerIdx);
+        pointerCoords.erase(pointerCoords.begin() + pointerIdx);
     }
     return events;
 }
@@ -648,6 +638,8 @@
             return memento.source & AINPUT_SOURCE_CLASS_POINTER;
         case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
             return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return memento.hovering;
         default:
             return false;
     }
@@ -657,7 +649,9 @@
     if (!state.mMotionMementos.empty()) {
         out << "mMotionMementos: ";
         for (const InputState::MotionMemento& memento : state.mMotionMementos) {
-            out << "{deviceId= " << memento.deviceId << ", hovering=" << memento.hovering << "}, ";
+            out << "{deviceId=" << memento.deviceId
+                << ", hovering=" << std::to_string(memento.hovering)
+                << ", downTime=" << memento.downTime << "}, ";
         }
     }
     return out;
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index d49469d..2808ba7 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -36,7 +36,7 @@
 
     // Returns true if the specified source is known to have received a hover enter
     // motion event.
-    bool isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const;
+    bool isHovering(DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId) const;
 
     // Records tracking information for a key event that has just been published.
     // Returns true if the event should be delivered, false if it is inconsistent
@@ -90,7 +90,7 @@
     struct KeyMemento {
         DeviceId deviceId;
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         int32_t keyCode;
         int32_t scanCode;
         int32_t metaState;
@@ -102,7 +102,7 @@
     struct MotionMemento {
         DeviceId deviceId;
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         int32_t flags;
         float xPrecision;
         float yPrecision;
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 35ad858..f9a2855 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -22,6 +22,8 @@
 #include <inttypes.h>
 #include <string>
 
+using android::base::Error;
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android::inputdispatcher {
@@ -35,28 +37,29 @@
 InputTarget::InputTarget(const std::shared_ptr<Connection>& connection, ftl::Flags<Flags> flags)
       : connection(connection), flags(flags) {}
 
-void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
-                              const ui::Transform& transform) {
+Result<void> InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
+                                      const ui::Transform& transform) {
     // The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
     // valid pointer property from the input event.
     if (newPointerIds.none()) {
         setDefaultPointerTransform(transform);
-        return;
+        return {};
     }
 
     // Ensure that the new set of pointers doesn't overlap with the current set of pointers.
     if ((getPointerIds() & newPointerIds).any()) {
-        LOG(FATAL) << __func__ << " - overlap with incoming pointers "
-                   << bitsetToString(newPointerIds) << " in " << *this;
+        return Error() << __func__ << " - overlap with incoming pointers "
+                       << bitsetToString(newPointerIds) << " in " << *this;
     }
 
     for (auto& [existingTransform, existingPointers] : mPointerTransforms) {
         if (transform == existingTransform) {
             existingPointers |= newPointerIds;
-            return;
+            return {};
         }
     }
     mPointerTransforms.emplace_back(transform, newPointerIds);
+    return {};
 }
 
 void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 058639a..90374f1 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -18,7 +18,6 @@
 
 #include <ftl/flags.h>
 #include <gui/WindowInfo.h>
-#include <gui/constants.h>
 #include <ui/Transform.h>
 #include <utils/BitSet.h>
 #include <bitset>
@@ -92,7 +91,8 @@
     InputTarget() = default;
     InputTarget(const std::shared_ptr<Connection>&, ftl::Flags<Flags> = {});
 
-    void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
+    android::base::Result<void> addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                            const ui::Transform& transform);
     void setDefaultPointerTransform(const ui::Transform& transform);
 
     /**
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index e09d97a..4ddd2e9 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -134,7 +134,9 @@
     mNumSketchEventsProcessed++;
 
     std::array<std::unique_ptr<KllQuantile>, SketchIndex::SIZE>& sketches =
-            timeline.isDown ? mDownSketches : mMoveSketches;
+            timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_DOWN
+            ? mDownSketches
+            : mMoveSketches;
 
     // Process common ones first
     const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
@@ -242,7 +244,9 @@
         const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
         const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
 
-        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED, timeline.isDown,
+        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED,
+                                   timeline.inputEventActionType ==
+                                           InputEventActionType::MOTION_ACTION_DOWN,
                                    static_cast<int32_t>(ns2us(eventToRead)),
                                    static_cast<int32_t>(ns2us(readToDeliver)),
                                    static_cast<int32_t>(ns2us(deliverToConsume)),
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 698bd9f..69024b3 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -67,9 +67,10 @@
     LOG_ALWAYS_FATAL_IF(processor == nullptr);
 }
 
-void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime,
-                                   nsecs_t readTime, DeviceId deviceId,
-                                   const std::set<InputDeviceUsageSource>& sources) {
+void LatencyTracker::trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime,
+                                   DeviceId deviceId,
+                                   const std::set<InputDeviceUsageSource>& sources,
+                                   int32_t inputEventAction, InputEventType inputEventType) {
     reportAndPruneMatureRecords(eventTime);
     const auto it = mTimelines.find(inputEventId);
     if (it != mTimelines.end()) {
@@ -101,9 +102,41 @@
         return;
     }
 
+    const InputEventActionType inputEventActionType = [&]() {
+        switch (inputEventType) {
+            case InputEventType::MOTION: {
+                switch (MotionEvent::getActionMasked(inputEventAction)) {
+                    case AMOTION_EVENT_ACTION_DOWN:
+                        return InputEventActionType::MOTION_ACTION_DOWN;
+                    case AMOTION_EVENT_ACTION_MOVE:
+                        return InputEventActionType::MOTION_ACTION_MOVE;
+                    case AMOTION_EVENT_ACTION_UP:
+                        return InputEventActionType::MOTION_ACTION_UP;
+                    case AMOTION_EVENT_ACTION_HOVER_MOVE:
+                        return InputEventActionType::MOTION_ACTION_HOVER_MOVE;
+                    case AMOTION_EVENT_ACTION_SCROLL:
+                        return InputEventActionType::MOTION_ACTION_SCROLL;
+                    default:
+                        return InputEventActionType::UNKNOWN_INPUT_EVENT;
+                }
+            }
+            case InputEventType::KEY: {
+                switch (inputEventAction) {
+                    case AKEY_EVENT_ACTION_DOWN:
+                    case AKEY_EVENT_ACTION_UP:
+                        return InputEventActionType::KEY;
+                    default:
+                        return InputEventActionType::UNKNOWN_INPUT_EVENT;
+                }
+            }
+            default:
+                return InputEventActionType::UNKNOWN_INPUT_EVENT;
+        }
+    }();
+
     mTimelines.emplace(inputEventId,
-                       InputEventTimeline(isDown, eventTime, readTime, identifier->vendor,
-                                          identifier->product, sources));
+                       InputEventTimeline(eventTime, readTime, identifier->vendor,
+                                          identifier->product, sources, inputEventActionType));
     mEventTimes.emplace(eventTime, inputEventId);
 }
 
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
index 890d61d..b4053ba 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.h
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -52,8 +52,9 @@
      * duplicate events that happen to have the same eventTime and inputEventId. Therefore, we
      * must drop all duplicate data.
      */
-    void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime,
-                       DeviceId deviceId, const std::set<InputDeviceUsageSource>& sources);
+    void trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime, DeviceId deviceId,
+                       const std::set<InputDeviceUsageSource>& sources, int32_t inputEventAction,
+                       InputEventType inputEventType);
     void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
                             nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
     void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index f8aa625..2bf63be 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -70,14 +70,14 @@
     });
 }
 
-void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
-                                   InputTarget::DispatchMode dispatchMode,
-                                   ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                                   const std::vector<PointerProperties>& touchingPointers,
-                                   std::optional<nsecs_t> firstDownTimeInTarget) {
+android::base::Result<void> TouchState::addOrUpdateWindow(
+        const sp<WindowInfoHandle>& windowHandle, InputTarget::DispatchMode dispatchMode,
+        ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
+        const std::vector<PointerProperties>& touchingPointers,
+        std::optional<nsecs_t> firstDownTimeInTarget) {
     if (touchingPointers.empty()) {
         LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
-        return;
+        return android::base::Error();
     }
     for (TouchedWindow& touchedWindow : windows) {
         // We do not compare windows by token here because two windows that share the same token
@@ -91,11 +91,12 @@
             // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
             // downTime set initially. Need to update existing window when a pointer is down for the
             // window.
-            touchedWindow.addTouchingPointers(deviceId, touchingPointers);
+            android::base::Result<void> addResult =
+                    touchedWindow.addTouchingPointers(deviceId, touchingPointers);
             if (firstDownTimeInTarget) {
                 touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
             }
-            return;
+            return addResult;
         }
     }
     TouchedWindow touchedWindow;
@@ -107,20 +108,22 @@
         touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
     }
     windows.push_back(touchedWindow);
+    return {};
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, const PointerProperties& pointer) {
+                                            DeviceId deviceId, const PointerProperties& pointer,
+                                            float x, float y) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, pointer);
+            touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, pointer);
+    touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
     windows.push_back(touchedWindow);
 }
 
@@ -178,9 +181,11 @@
     clearWindowsWithoutPointers();
 }
 
-sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             return window.windowHandle;
         }
@@ -188,10 +193,13 @@
     return nullptr;
 }
 
-bool TouchState::isSlippery() const {
+bool TouchState::isSlippery(DeviceId deviceId) const {
     // Must have exactly one foreground window.
     bool haveSlipperyForegroundWindow = false;
     for (const TouchedWindow& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
             if (haveSlipperyForegroundWindow ||
                 !window.windowHandle->getInfo()->inputConfig.test(
@@ -204,9 +212,11 @@
     return haveSlipperyForegroundWindow;
 }
 
-sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
-    for (size_t i = 0; i < windows.size(); i++) {
-        const TouchedWindow& window = windows[i];
+sp<WindowInfoHandle> TouchState::getWallpaperWindow(DeviceId deviceId) const {
+    for (const auto& window : windows) {
+        if (!window.hasTouchingPointers(deviceId)) {
+            continue;
+        }
         if (window.windowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
             return window.windowHandle;
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 3d534bc..451d917 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -43,13 +43,14 @@
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
                                          const sp<android::gui::WindowInfoHandle>& windowHandle);
-    void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                           InputTarget::DispatchMode dispatchMode,
-                           ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
-                           const std::vector<PointerProperties>& touchingPointers,
-                           std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
+    android::base::Result<void> addOrUpdateWindow(
+            const sp<android::gui::WindowInfoHandle>& windowHandle,
+            InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+            DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
+            std::optional<nsecs_t> firstDownTimeInTarget);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, const PointerProperties& pointer);
+                                    DeviceId deviceId, const PointerProperties& pointer, float x,
+                                    float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
@@ -64,9 +65,9 @@
     // set to false.
     void cancelPointersForNonPilferingWindows();
 
-    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
-    bool isSlippery() const;
-    sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
+    sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle(DeviceId deviceId) const;
+    bool isSlippery(DeviceId deviceId) const;
+    sp<android::gui::WindowInfoHandle> getWallpaperWindow(DeviceId deviceId) const;
     const TouchedWindow& getTouchedWindow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const;
     // Whether any of the windows are currently being touched
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 037d7c8..fa5be1a 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -20,6 +20,7 @@
 #include <android-base/stringprintf.h>
 #include <input/PrintTools.h>
 
+using android::base::Result;
 using android::base::StringPrintf;
 
 namespace android {
@@ -35,6 +36,13 @@
                         }) != pointers.end();
 }
 
+bool hasPointerId(const std::vector<TouchedWindow::HoveringPointer>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const TouchedWindow::HoveringPointer& pointer) {
+                            return pointer.properties.id == pointerId;
+                        }) != pointers.end();
+}
+
 } // namespace
 
 bool TouchedWindow::hasHoveringPointers() const {
@@ -77,20 +85,22 @@
     return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
-    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& properties,
+                                       float x, float y) {
+    std::vector<HoveringPointer>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
     const size_t initialSize = hoveringPointers.size();
-    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
-        return properties.id == pointer.id;
+    std::erase_if(hoveringPointers, [&properties](const HoveringPointer& pointer) {
+        return pointer.properties.id == properties.id;
     });
     if (hoveringPointers.size() != initialSize) {
-        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+        LOG(ERROR) << __func__ << ": " << properties << ", device " << deviceId << " was in "
+                   << *this;
     }
-    hoveringPointers.push_back(pointer);
+    hoveringPointers.push_back({properties, x, y});
 }
 
-void TouchedWindow::addTouchingPointers(DeviceId deviceId,
-                                        const std::vector<PointerProperties>& pointers) {
+Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
+                                                const std::vector<PointerProperties>& pointers) {
     std::vector<PointerProperties>& touchingPointers = mDeviceStates[deviceId].touchingPointers;
     const size_t initialSize = touchingPointers.size();
     for (const PointerProperties& pointer : pointers) {
@@ -98,11 +108,14 @@
             return properties.id == pointer.id;
         });
     }
-    if (touchingPointers.size() != initialSize) {
+    const bool foundInconsistentState = touchingPointers.size() != initialSize;
+    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
+    if (foundInconsistentState) {
         LOG(ERROR) << __func__ << ": " << dumpVector(pointers, streamableToString) << ", device "
                    << deviceId << " already in " << *this;
+        return android::base::Error();
     }
-    touchingPointers.insert(touchingPointers.end(), pointers.begin(), pointers.end());
+    return {};
 }
 
 bool TouchedWindow::hasTouchingPointers() const {
@@ -169,8 +182,8 @@
                 return true;
             }
         }
-        for (const PointerProperties& properties : state.hoveringPointers) {
-            if (properties.toolType == ToolType::STYLUS) {
+        for (const HoveringPointer& pointer : state.hoveringPointers) {
+            if (pointer.properties.toolType == ToolType::STYLUS) {
                 return true;
             }
         }
@@ -266,8 +279,8 @@
     }
     DeviceState& state = stateIt->second;
 
-    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
-        return properties.id == pointerId;
+    std::erase_if(state.hoveringPointers, [&pointerId](const HoveringPointer& pointer) {
+        return pointer.properties.id == pointerId;
     });
 
     if (!state.hasPointers()) {
@@ -275,6 +288,22 @@
     }
 }
 
+std::vector<DeviceId> TouchedWindow::eraseHoveringPointersIf(
+        std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition) {
+    std::vector<DeviceId> erasedDevices;
+    for (auto& [deviceId, state] : mDeviceStates) {
+        std::erase_if(state.hoveringPointers, [&](const HoveringPointer& pointer) {
+            if (condition(pointer.properties, pointer.x, pointer.y)) {
+                erasedDevices.push_back(deviceId);
+                return true;
+            }
+            return false;
+        });
+    }
+
+    return erasedDevices;
+}
+
 void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
@@ -308,6 +337,11 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow::HoveringPointer& pointer) {
+    out << pointer.properties << " at (" << pointer.x << ", " << pointer.y << ")";
+    return out;
+}
+
 std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
     out << window.dump();
     return out;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 0d1531f..c38681e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,7 +38,7 @@
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer, float x, float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
@@ -46,7 +46,8 @@
     bool hasTouchingPointers() const;
     bool hasTouchingPointers(DeviceId deviceId) const;
     std::vector<PointerProperties> getTouchingPointers(DeviceId deviceId) const;
-    void addTouchingPointers(DeviceId deviceId, const std::vector<PointerProperties>& pointers);
+    android::base::Result<void> addTouchingPointers(DeviceId deviceId,
+                                                    const std::vector<PointerProperties>& pointers);
     void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
     void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
     bool hasActiveStylus() const;
@@ -68,6 +69,15 @@
     void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
+    struct HoveringPointer {
+        PointerProperties properties;
+        float x;
+        float y;
+    };
+
+    std::vector<DeviceId> eraseHoveringPointersIf(
+            std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition);
+
 private:
     struct DeviceState {
         std::vector<PointerProperties> touchingPointers;
@@ -77,7 +87,7 @@
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::vector<PointerProperties> hoveringPointers;
+        std::vector<HoveringPointer> hoveringPointers;
 
         bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
index 5eb3a32..ba197d4 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
@@ -34,8 +34,13 @@
     // The key repeat inter-key delay.
     nsecs_t keyRepeatDelay;
 
+    // Whether key repeat is enabled.
+    bool keyRepeatEnabled;
+
     InputDispatcherConfiguration()
-          : keyRepeatTimeout(500 * 1000000LL), keyRepeatDelay(50 * 1000000LL) {}
+          : keyRepeatTimeout(500 * 1000000LL),
+            keyRepeatDelay(50 * 1000000LL),
+            keyRepeatEnabled(true) {}
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 7c440e8..463a952 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -92,14 +92,14 @@
      * This method may be called on any thread (usually by the input manager).
      */
     virtual void setFocusedApplication(
-            int32_t displayId,
+            ui::LogicalDisplayId displayId,
             const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) = 0;
 
     /* Sets the focused display.
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual void setFocusedDisplay(int32_t displayId) = 0;
+    virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
 
     /** Sets the minimum time between user activity pokes. */
     virtual void setMinTimeBetweenUserActivityPokes(std::chrono::milliseconds interval) = 0;
@@ -130,7 +130,7 @@
      * Returns true when changing touch mode state.
      */
     virtual bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
-                                int32_t displayId) = 0;
+                                ui::LogicalDisplayId displayId) = 0;
 
     /**
      * Sets the maximum allowed obscuring opacity by UID to propagate touches.
@@ -156,7 +156,8 @@
      * Returns true on success, false if there was no on-going touch on the display.
      * @deprecated
      */
-    virtual bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken, int32_t displayId) = 0;
+    virtual bool transferTouchOnDisplay(const sp<IBinder>& destChannelToken,
+                                        ui::LogicalDisplayId displayId) = 0;
 
     /**
      * Sets focus on the specified window.
@@ -179,9 +180,8 @@
      *
      * This method may be called on any thread (usually by the input manager).
      */
-    virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
-                                                                           const std::string& name,
-                                                                           gui::Pid pid) = 0;
+    virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(
+            ui::LogicalDisplayId displayId, const std::string& name, gui::Pid pid) = 0;
 
     /* Removes input channels that will no longer receive input events.
      *
@@ -207,7 +207,8 @@
      * ineligible, all attempts to request pointer capture for windows on that display will fail.
      *  TODO(b/214621487): Remove or move to a display flag.
      */
-    virtual void setDisplayEligibilityForPointerCapture(int displayId, bool isEligible) = 0;
+    virtual void setDisplayEligibilityForPointerCapture(ui::LogicalDisplayId displayId,
+                                                        bool isEligible) = 0;
 
     /* Flush input device motion sensor.
      *
@@ -218,7 +219,7 @@
     /**
      * Called when a display has been removed from the system.
      */
-    virtual void displayRemoved(int32_t displayId) = 0;
+    virtual void displayRemoved(ui::LogicalDisplayId displayId) = 0;
 
     /*
      * Abort the current touch stream.
@@ -226,16 +227,22 @@
     virtual void cancelCurrentTouch() = 0;
 
     /*
-     * Updates key repeat configuration timeout and delay.
+     * Updates whether key repeat is enabled and key repeat configuration timeout and delay.
      */
     virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                           std::chrono::nanoseconds delay) = 0;
+                                           std::chrono::nanoseconds delay,
+                                           bool keyRepeatEnabled) = 0;
 
     /*
      * Determine if a pointer from a device is being dispatched to the given window.
      */
-    virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
-                                   int32_t pointerId) = 0;
+    virtual bool isPointerInWindow(const sp<IBinder>& token, ui::LogicalDisplayId displayId,
+                                   DeviceId deviceId, int32_t pointerId) = 0;
+
+    /*
+     * Notify the dispatcher that the state of the input method connection changed.
+     */
+    virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 62c2b02..b885ba1 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -43,9 +43,6 @@
     InputDispatcherPolicyInterface() = default;
     virtual ~InputDispatcherPolicyInterface() = default;
 
-    /* Notifies the system that a configuration change has occurred. */
-    virtual void notifyConfigurationChanged(nsecs_t when) = 0;
-
     /* Notifies the system that an application does not have a focused window.
      */
     virtual void notifyNoFocusedWindowAnr(
@@ -76,6 +73,11 @@
                                       InputDeviceSensorAccuracy accuracy) = 0;
     virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0;
 
+    /*
+     * Notifies the system that focused display has changed.
+     */
+    virtual void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) = 0;
+
     /* Filters an input event.
      * Return true to dispatch the event unmodified, false to consume the event.
      * A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
@@ -99,8 +101,9 @@
      * This method is expected to set the POLICY_FLAG_PASS_TO_USER policy flag if the event
      * should be dispatched to applications.
      */
-    virtual void interceptMotionBeforeQueueing(int32_t displayId, uint32_t source, int32_t action,
-                                               nsecs_t when, uint32_t& policyFlags) = 0;
+    virtual void interceptMotionBeforeQueueing(ui::LogicalDisplayId displayId, uint32_t source,
+                                               int32_t action, nsecs_t when,
+                                               uint32_t& policyFlags) = 0;
 
     /* Allows the policy a chance to intercept a key before dispatching. */
     virtual nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>& token,
@@ -119,7 +122,8 @@
                               uint32_t policyFlags) = 0;
 
     /* Poke user activity for an event dispatched to a window. */
-    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
+    virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                  ui::LogicalDisplayId displayId) = 0;
 
     /*
      * Return true if the provided event is stale, and false otherwise. Used for determining
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
index a61fa85..0b17507 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.cpp
@@ -21,21 +21,43 @@
 
 namespace android::inputdispatcher::trace {
 
+namespace {
+
+using namespace ftl::flag_operators;
+
+// The trace config to use for maximal tracing.
+const impl::TraceConfig CONFIG_TRACE_ALL{
+        .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
+                impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
+        .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
+                                  .matchAllPackages = {},
+                                  .matchAnyPackages = {},
+                                  .matchSecure{},
+                                  .matchImeConnectionActive = {}}},
+};
+
+} // namespace
+
 void AndroidInputEventProtoConverter::toProtoMotionEvent(const TracedMotionEvent& event,
-                                                         proto::AndroidMotionEvent& outProto) {
+                                                         proto::AndroidMotionEvent& outProto,
+                                                         bool isRedacted) {
     outProto.set_event_id(event.id);
     outProto.set_event_time_nanos(event.eventTime);
     outProto.set_down_time_nanos(event.downTime);
     outProto.set_source(event.source);
     outProto.set_action(event.action);
     outProto.set_device_id(event.deviceId);
-    outProto.set_display_id(event.displayId);
+    outProto.set_display_id(event.displayId.val());
     outProto.set_classification(static_cast<int32_t>(event.classification));
-    outProto.set_cursor_position_x(event.xCursorPosition);
-    outProto.set_cursor_position_y(event.yCursorPosition);
     outProto.set_flags(event.flags);
     outProto.set_policy_flags(event.policyFlags);
 
+    if (!isRedacted) {
+        outProto.set_cursor_position_x(event.xCursorPosition);
+        outProto.set_cursor_position_y(event.yCursorPosition);
+        outProto.set_meta_state(event.metaState);
+    }
+
     for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
         auto* pointer = outProto.add_pointer();
 
@@ -49,36 +71,46 @@
             const auto axis = bits.clearFirstMarkedBit();
             auto axisEntry = pointer->add_axis_value();
             axisEntry->set_axis(axis);
-            axisEntry->set_value(coords.values[axisIndex]);
+
+            if (!isRedacted) {
+                axisEntry->set_value(coords.values[axisIndex]);
+            }
         }
     }
 }
 
 void AndroidInputEventProtoConverter::toProtoKeyEvent(const TracedKeyEvent& event,
-                                                      proto::AndroidKeyEvent& outProto) {
+                                                      proto::AndroidKeyEvent& outProto,
+                                                      bool isRedacted) {
     outProto.set_event_id(event.id);
     outProto.set_event_time_nanos(event.eventTime);
     outProto.set_down_time_nanos(event.downTime);
     outProto.set_source(event.source);
     outProto.set_action(event.action);
     outProto.set_device_id(event.deviceId);
-    outProto.set_display_id(event.displayId);
-    outProto.set_key_code(event.keyCode);
-    outProto.set_scan_code(event.scanCode);
-    outProto.set_meta_state(event.metaState);
+    outProto.set_display_id(event.displayId.val());
     outProto.set_repeat_count(event.repeatCount);
     outProto.set_flags(event.flags);
     outProto.set_policy_flags(event.policyFlags);
+
+    if (!isRedacted) {
+        outProto.set_key_code(event.keyCode);
+        outProto.set_scan_code(event.scanCode);
+        outProto.set_meta_state(event.metaState);
+    }
 }
 
 void AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(
-        const InputTracingBackendInterface::WindowDispatchArgs& args,
-        proto::AndroidWindowInputDispatchEvent& outProto) {
+        const WindowDispatchArgs& args, proto::AndroidWindowInputDispatchEvent& outProto,
+        bool isRedacted) {
     std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
     outProto.set_vsync_id(args.vsyncId);
     outProto.set_window_id(args.windowId);
     outProto.set_resolved_flags(args.resolvedFlags);
 
+    if (isRedacted) {
+        return;
+    }
     if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
         for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
             auto* pointerProto = outProto.add_dispatched_pointer();
@@ -91,7 +123,8 @@
 
             const auto& coords = motion->pointerCoords[i];
             const auto coordsInWindow =
-                    MotionEvent::calculateTransformedCoords(motion->source, args.transform, coords);
+                    MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
+                                                            args.transform, coords);
             auto bits = BitSet64(coords.bits);
             for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
                 const uint32_t axis = bits.clearFirstMarkedBit();
@@ -106,4 +139,65 @@
     }
 }
 
+impl::TraceConfig AndroidInputEventProtoConverter::parseConfig(
+        proto::AndroidInputEventConfig::Decoder& protoConfig) {
+    if (protoConfig.has_mode() &&
+        protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
+        // User has requested the preset for maximal tracing
+        return CONFIG_TRACE_ALL;
+    }
+
+    impl::TraceConfig config;
+
+    // Parse trace flags
+    if (protoConfig.has_trace_dispatcher_input_events() &&
+        protoConfig.trace_dispatcher_input_events()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
+    }
+    if (protoConfig.has_trace_dispatcher_window_dispatch() &&
+        protoConfig.trace_dispatcher_window_dispatch()) {
+        config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
+    }
+
+    // Parse trace rules
+    auto rulesIt = protoConfig.rules();
+    while (rulesIt) {
+        proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
+        config.rules.emplace_back();
+        auto& rule = config.rules.back();
+
+        rule.level = protoRule.has_trace_level()
+                ? static_cast<impl::TraceLevel>(protoRule.trace_level())
+                : impl::TraceLevel::TRACE_LEVEL_NONE;
+
+        if (protoRule.has_match_all_packages()) {
+            auto pkgIt = protoRule.match_all_packages();
+            while (pkgIt) {
+                rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_any_packages()) {
+            auto pkgIt = protoRule.match_any_packages();
+            while (pkgIt) {
+                rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
+                pkgIt++;
+            }
+        }
+
+        if (protoRule.has_match_secure()) {
+            rule.matchSecure = protoRule.match_secure();
+        }
+
+        if (protoRule.has_match_ime_connection_active()) {
+            rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
+        }
+
+        rulesIt++;
+    }
+
+    return config;
+}
+
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
index 8a46f15..887913f 100644
--- a/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
+++ b/services/inputflinger/dispatcher/trace/AndroidInputEventProtoConverter.h
@@ -16,9 +16,11 @@
 
 #pragma once
 
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
 
 #include "InputTracingBackendInterface.h"
+#include "InputTracingPerfettoBackendConfig.h"
 
 namespace proto = perfetto::protos::pbzero;
 
@@ -30,10 +32,14 @@
 class AndroidInputEventProtoConverter {
 public:
     static void toProtoMotionEvent(const TracedMotionEvent& event,
-                                   proto::AndroidMotionEvent& outProto);
-    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto);
-    static void toProtoWindowDispatchEvent(const InputTracingBackendInterface::WindowDispatchArgs&,
-                                           proto::AndroidWindowInputDispatchEvent& outProto);
+                                   proto::AndroidMotionEvent& outProto, bool isRedacted);
+    static void toProtoKeyEvent(const TracedKeyEvent& event, proto::AndroidKeyEvent& outProto,
+                                bool isRedacted);
+    static void toProtoWindowDispatchEvent(const WindowDispatchArgs&,
+                                           proto::AndroidWindowInputDispatchEvent& outProto,
+                                           bool isRedacted);
+
+    static impl::TraceConfig parseConfig(proto::AndroidInputEventConfig::Decoder& protoConfig);
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index be09013..5d2b854 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -19,6 +19,7 @@
 #include "InputTracer.h"
 
 #include <android-base/logging.h>
+#include <private/android_filesystem_config.h>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -30,7 +31,7 @@
     using V::operator()...;
 };
 
-TracedEvent createTracedEvent(const MotionEntry& e) {
+TracedEvent createTracedEvent(const MotionEntry& e, EventType type) {
     return TracedMotionEvent{e.id,
                              e.eventTime,
                              e.policyFlags,
@@ -50,13 +51,45 @@
                              e.yCursorPosition,
                              e.downTime,
                              e.pointerProperties,
-                             e.pointerCoords};
+                             e.pointerCoords,
+                             type};
 }
 
-TracedEvent createTracedEvent(const KeyEntry& e) {
+TracedEvent createTracedEvent(const KeyEntry& e, EventType type) {
     return TracedKeyEvent{e.id,        e.eventTime, e.policyFlags, e.deviceId, e.source,
                           e.displayId, e.action,    e.keyCode,     e.scanCode, e.metaState,
-                          e.downTime,  e.flags,     e.repeatCount};
+                          e.downTime,  e.flags,     e.repeatCount, type};
+}
+
+void writeEventToBackend(const TracedEvent& event, const TracedEventMetadata metadata,
+                         InputTracingBackendInterface& backend) {
+    std::visit(Visitor{[&](const TracedMotionEvent& e) { backend.traceMotionEvent(e, metadata); },
+                       [&](const TracedKeyEvent& e) { backend.traceKeyEvent(e, metadata); }},
+               event);
+}
+
+inline auto getId(const trace::TracedEvent& v) {
+    return std::visit([](const auto& event) { return event.id; }, v);
+}
+
+// Helper class to extract relevant information from InputTarget.
+struct InputTargetInfo {
+    gui::Uid uid;
+    bool isSecureWindow;
+};
+
+InputTargetInfo getTargetInfo(const InputTarget& target) {
+    if (target.windowHandle == nullptr) {
+        if (!target.connection->monitor) {
+            LOG(FATAL) << __func__ << ": Window is not set for non-monitor target";
+        }
+        // This is a global monitor, assume its target is the system.
+        return {.uid = gui::Uid{AID_SYSTEM}, .isSecureWindow = false};
+    }
+    const auto& info = *target.windowHandle->getInfo();
+    const bool isSensitiveTarget =
+            info.inputConfig.test(gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY);
+    return {target.windowHandle->getInfo()->ownerUid, isSensitiveTarget};
 }
 
 } // namespace
@@ -67,97 +100,186 @@
       : mBackend(std::move(backend)) {}
 
 std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
-    TracedEvent traced;
+    // This is a newly traced inbound event. Create a new state to track it and its derived events.
+    auto eventState = std::make_shared<EventState>(*this);
 
     if (entry.type == EventEntry::Type::MOTION) {
         const auto& motion = static_cast<const MotionEntry&>(entry);
-        traced = createTracedEvent(motion);
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::INBOUND));
     } else if (entry.type == EventEntry::Type::KEY) {
         const auto& key = static_cast<const KeyEntry&>(entry);
-        traced = createTracedEvent(key);
+        eventState->events.emplace_back(createTracedEvent(key, EventType::INBOUND));
     } else {
         LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
     }
 
-    return std::make_unique<EventTrackerImpl>(*this, std::move(traced));
+    return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/false);
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::createTrackerForSyntheticEvent() {
+    // Create a new EventState to track events derived from this tracker.
+    return std::make_unique<EventTrackerImpl>(std::make_shared<EventState>(*this),
+                                              /*isDerived=*/false);
 }
 
 void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
                                        const InputTarget& target) {
-    auto& cookieState = getState(cookie);
-    if (!cookieState) {
-        LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()";
+    auto& eventState = getState(cookie);
+    const InputTargetInfo& targetInfo = getTargetInfo(target);
+    if (eventState->isEventProcessingComplete) {
+        // Disallow adding new targets after eventProcessingComplete() is called.
+        if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
+            LOG(FATAL) << __func__ << ": Cannot add new target after eventProcessingComplete";
+        }
+        return;
     }
-    // TODO(b/210460522): Determine if the event is sensitive based on the target.
+    if (isDerivedCookie(cookie)) {
+        // Disallow adding new targets from a derived cookie.
+        if (eventState->metadata.targets.count(targetInfo.uid) == 0) {
+            LOG(FATAL) << __func__ << ": Cannot add new target from a derived cookie";
+        }
+        return;
+    }
+
+    eventState->metadata.targets.emplace(targetInfo.uid);
+    eventState->metadata.isSecure |= targetInfo.isSecureWindow;
 }
 
-void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
-    auto& cookieState = getState(cookie);
-    if (!cookieState) {
+void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie,
+                                          nsecs_t processingTimestamp) {
+    if (isDerivedCookie(cookie)) {
+        LOG(FATAL) << "Event processing cannot be set from a derived cookie.";
+    }
+    auto& eventState = getState(cookie);
+    if (eventState->isEventProcessingComplete) {
         LOG(FATAL) << "Traced event was already logged. "
                       "eventProcessingComplete() was likely called more than once.";
     }
-
-    std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
-                       [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
-               cookieState->event);
-    cookieState.reset();
+    eventState->onEventProcessingComplete(processingTimestamp);
 }
 
-void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
-                                     const EventTrackerInterface* cookie) {
-    const EventEntry& entry = *dispatchEntry.eventEntry;
+std::unique_ptr<EventTrackerInterface> InputTracer::traceDerivedEvent(
+        const EventEntry& entry, const EventTrackerInterface& originalEventCookie) {
+    // This is an event derived from an already-established event. Use the same state to track
+    // this event too.
+    auto eventState = getState(originalEventCookie);
 
-    TracedEvent traced;
     if (entry.type == EventEntry::Type::MOTION) {
         const auto& motion = static_cast<const MotionEntry&>(entry);
-        traced = createTracedEvent(motion);
+        eventState->events.emplace_back(createTracedEvent(motion, EventType::SYNTHESIZED));
     } else if (entry.type == EventEntry::Type::KEY) {
         const auto& key = static_cast<const KeyEntry&>(entry);
-        traced = createTracedEvent(key);
+        eventState->events.emplace_back(createTracedEvent(key, EventType::SYNTHESIZED));
     } else {
         LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
     }
 
-    if (!cookie) {
-        // This event was not tracked as an inbound event, so trace it now.
-        std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
-                           [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
-                   traced);
+    if (eventState->isEventProcessingComplete) {
+        // It is possible for a derived event to be dispatched some time after the original event
+        // is dispatched, such as in the case of key fallback events. To account for these cases,
+        // derived events can be traced after the processing is complete for the original event.
+        const auto& event = eventState->events.back();
+        writeEventToBackend(event, eventState->metadata, *mBackend);
+    }
+    return std::make_unique<EventTrackerImpl>(std::move(eventState), /*isDerived=*/true);
+}
+
+void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
+                                     const EventTrackerInterface& cookie) {
+    auto& eventState = getState(cookie);
+    const EventEntry& entry = *dispatchEntry.eventEntry;
+    const int32_t eventId = entry.id;
+    // TODO(b/328618922): Remove resolved key repeats after making repeatCount non-mutable.
+    // The KeyEntry's repeatCount is mutable and can be modified after an event is initially traced,
+    // so we need to find the repeatCount at the time of dispatching to trace it accurately.
+    int32_t resolvedKeyRepeatCount = 0;
+    if (entry.type == EventEntry::Type::KEY) {
+        resolvedKeyRepeatCount = static_cast<const KeyEntry&>(entry).repeatCount;
+    }
+
+    auto tracedEventIt =
+            std::find_if(eventState->events.begin(), eventState->events.end(),
+                         [eventId](const auto& event) { return eventId == getId(event); });
+    if (tracedEventIt == eventState->events.end()) {
+        LOG(FATAL)
+                << __func__
+                << ": Failed to find a previously traced event that matches the dispatched event";
+    }
+
+    if (eventState->metadata.targets.count(dispatchEntry.targetUid) == 0) {
+        LOG(FATAL) << __func__ << ": Event is being dispatched to UID that it is not targeting";
     }
 
     // The vsyncId only has meaning if the event is targeting a window.
     const int32_t windowId = dispatchEntry.windowId.value_or(0);
     const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
 
-    mBackend->traceWindowDispatch({std::move(traced), dispatchEntry.deliveryTime,
-                                   dispatchEntry.resolvedFlags, dispatchEntry.targetUid, vsyncId,
-                                   windowId, dispatchEntry.transform, dispatchEntry.rawTransform,
-                                   /*hmac=*/{}});
+    // TODO(b/210460522): Pass HMAC into traceEventDispatch.
+    const WindowDispatchArgs windowDispatchArgs{*tracedEventIt,
+                                                dispatchEntry.deliveryTime,
+                                                dispatchEntry.resolvedFlags,
+                                                dispatchEntry.targetUid,
+                                                vsyncId,
+                                                windowId,
+                                                dispatchEntry.transform,
+                                                dispatchEntry.rawTransform,
+                                                /*hmac=*/{},
+                                                resolvedKeyRepeatCount};
+    if (eventState->isEventProcessingComplete) {
+        mBackend->traceWindowDispatch(std::move(windowDispatchArgs), eventState->metadata);
+    } else {
+        eventState->pendingDispatchArgs.emplace_back(std::move(windowDispatchArgs));
+    }
 }
 
-std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) {
+std::shared_ptr<InputTracer::EventState>& InputTracer::getState(
+        const EventTrackerInterface& cookie) {
     return static_cast<const EventTrackerImpl&>(cookie).mState;
 }
 
-// --- InputTracer::EventTrackerImpl ---
+bool InputTracer::isDerivedCookie(const EventTrackerInterface& cookie) {
+    return static_cast<const EventTrackerImpl&>(cookie).mIsDerived;
+}
 
-InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event)
-      : mTracer(tracer), mState(event) {}
+// --- InputTracer::EventState ---
 
-InputTracer::EventTrackerImpl::~EventTrackerImpl() {
-    if (!mState) {
+void InputTracer::EventState::onEventProcessingComplete(nsecs_t processingTimestamp) {
+    metadata.processingTimestamp = processingTimestamp;
+    metadata.isImeConnectionActive = tracer.mIsImeConnectionActive;
+
+    // Write all of the events known so far to the trace.
+    for (const auto& event : events) {
+        writeEventToBackend(event, metadata, *tracer.mBackend);
+    }
+    // Write all pending dispatch args to the trace.
+    for (const auto& windowDispatchArgs : pendingDispatchArgs) {
+        auto tracedEventIt =
+                std::find_if(events.begin(), events.end(),
+                             [id = getId(windowDispatchArgs.eventEntry)](const auto& event) {
+                                 return id == getId(event);
+                             });
+        if (tracedEventIt == events.end()) {
+            LOG(FATAL) << __func__
+                       << ": Failed to find a previously traced event that matches the dispatched "
+                          "event";
+        }
+        tracer.mBackend->traceWindowDispatch(windowDispatchArgs, metadata);
+    }
+    pendingDispatchArgs.clear();
+
+    isEventProcessingComplete = true;
+}
+
+InputTracer::EventState::~EventState() {
+    if (isEventProcessingComplete) {
         // This event has already been written to the trace as expected.
         return;
     }
-    // We're still holding on to the state, which means it hasn't yet been written to the trace.
-    // Write it to the trace now.
-    // TODO(b/210460522): Determine why/where the event is being destroyed before
-    //   eventProcessingComplete() is called.
-    std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); },
-                       [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }},
-               mState->event);
-    mState.reset();
+    // The event processing was never marked as complete, so do it now.
+    // We should never end up here in normal operation. However, in tests, it's possible that we
+    // stop and destroy InputDispatcher without waiting for it to finish processing events, at
+    // which point an event (and thus its EventState) may be destroyed before processing finishes.
+    onEventProcessingComplete(systemTime(CLOCK_MONOTONIC));
 }
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index c8b25c9..96e619c 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -42,37 +42,55 @@
     InputTracer& operator=(const InputTracer&) = delete;
 
     std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) override;
+    std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() override;
     void dispatchToTargetHint(const EventTrackerInterface&, const InputTarget&) override;
-    void eventProcessingComplete(const EventTrackerInterface&) override;
-    void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override;
+    void eventProcessingComplete(const EventTrackerInterface&,
+                                 nsecs_t processingTimestamp) override;
+    std::unique_ptr<EventTrackerInterface> traceDerivedEvent(const EventEntry&,
+                                                             const EventTrackerInterface&) override;
+    void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) override;
+    void setInputMethodConnectionIsActive(bool isActive) override {
+        mIsImeConnectionActive = isActive;
+    }
 
 private:
     std::unique_ptr<InputTracingBackendInterface> mBackend;
+    bool mIsImeConnectionActive{false};
 
-    // The state of a tracked event.
+    // The state of a tracked event, shared across all events derived from the original event.
     struct EventState {
-        const TracedEvent event;
-        // TODO(b/210460522): Add additional args for tracking event sensitivity and
-        //  dispatch target UIDs.
+        explicit inline EventState(InputTracer& tracer) : tracer(tracer){};
+        ~EventState();
+
+        void onEventProcessingComplete(nsecs_t processingTimestamp);
+
+        InputTracer& tracer;
+        std::vector<TracedEvent> events;
+        bool isEventProcessingComplete{false};
+        // A queue to hold dispatch args from being traced until event processing is complete.
+        std::vector<WindowDispatchArgs> pendingDispatchArgs;
+        // The metadata should not be modified after event processing is complete.
+        TracedEventMetadata metadata{};
     };
 
     // Get the event state associated with a tracking cookie.
-    std::optional<EventState>& getState(const EventTrackerInterface&);
+    std::shared_ptr<EventState>& getState(const EventTrackerInterface&);
+    bool isDerivedCookie(const EventTrackerInterface&);
 
     // Implementation of the event tracker cookie. The cookie holds the event state directly for
     // convenience to avoid the overhead of tracking the state separately in InputTracer.
     class EventTrackerImpl : public EventTrackerInterface {
     public:
-        explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry);
-        virtual ~EventTrackerImpl() override;
+        inline EventTrackerImpl(const std::shared_ptr<EventState>& state, bool isDerivedEvent)
+              : mState(state), mIsDerived(isDerivedEvent) {}
+        EventTrackerImpl(const EventTrackerImpl&) = default;
 
     private:
-        InputTracer& mTracer;
-        // This event tracker cookie will only hold the state as long as it has not been written
-        // to the trace. The state is released when the event is written to the trace.
-        mutable std::optional<EventState> mState;
+        mutable std::shared_ptr<EventState> mState;
+        const bool mIsDerived;
 
-        friend std::optional<EventState>& InputTracer::getState(const EventTrackerInterface&);
+        friend std::shared_ptr<EventState>& InputTracer::getState(const EventTrackerInterface&);
+        friend bool InputTracer::isDerivedCookie(const EventTrackerInterface&);
     };
 };
 
diff --git a/services/inputflinger/dispatcher/trace/InputTracerInterface.h b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
index c6cd7de..f5e4e59 100644
--- a/services/inputflinger/dispatcher/trace/InputTracerInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracerInterface.h
@@ -54,6 +54,14 @@
     virtual std::unique_ptr<EventTrackerInterface> traceInboundEvent(const EventEntry&) = 0;
 
     /**
+     * Create a trace tracker for a synthetic event that does not stem from an inbound input event.
+     * This includes things like generating cancellations or down events for various reasons,
+     * such as ANR, pilfering, transfer touch, etc. Any key or motion events generated for this
+     * synthetic event should be traced as a derived event using {@link #traceDerivedEvent}.
+     */
+    virtual std::unique_ptr<EventTrackerInterface> createTrackerForSyntheticEvent() = 0;
+
+    /**
      * Notify the tracer that the traced event will be sent to the given InputTarget.
      * The tracer may change how the event is logged depending on the target. For example,
      * events targeting certain UIDs may be logged as sensitive events.
@@ -73,15 +81,34 @@
      * outside of our control, such as how long apps take to respond, so we don't want to depend on
      * that.
      */
-    virtual void eventProcessingComplete(const EventTrackerInterface&) = 0;
+    virtual void eventProcessingComplete(const EventTrackerInterface&,
+                                         nsecs_t processingTimestamp) = 0;
+
+    /**
+     * Trace an input event that is derived from another event. This is used in cases where an event
+     * is modified from the original, such as when a touch is split across multiple windows, or
+     * when a HOVER_MOVE event is modified to be a HOVER_EXIT, etc. The original event's tracker
+     * must be provided, and a new EventTracker is returned that should be used to track the event's
+     * lifecycle.
+     *
+     * NOTE: The derived tracker cannot be used to change the targets of the original event, meaning
+     * it cannot be used with {@link #dispatchToTargetHint} or {@link eventProcessingComplete}.
+     */
+    virtual std::unique_ptr<EventTrackerInterface> traceDerivedEvent(
+            const EventEntry&, const EventTrackerInterface& originalEventTracker) = 0;
 
     /**
      * Trace an input event being successfully dispatched to a window. The dispatched event may
-     * be a previously traced inbound event, or it may be a synthesized event that has not been
-     * previously traced. For inbound events that were previously traced, the EventTracker cookie
-     * must be provided. For events that were not previously traced, the cookie must be null.
+     * be a previously traced inbound event, or it may be a synthesized event. All dispatched events
+     * must have been previously traced, so the trace tracker associated with the event must be
+     * provided.
      */
-    virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) = 0;
+    virtual void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface&) = 0;
+
+    /**
+     * Notify that the state of the input method connection changed.
+     */
+    virtual void setInputMethodConnectionIsActive(bool isActive) = 0;
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index b0eadfe..761d619 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -21,12 +21,27 @@
 #include <ui/Transform.h>
 
 #include <array>
+#include <set>
 #include <variant>
 #include <vector>
 
 namespace android::inputdispatcher::trace {
 
 /**
+ * Describes the type of this event being traced, with respect to InputDispatcher.
+ */
+enum class EventType {
+    // This is an event that was reported through the InputListener interface or was injected.
+    INBOUND,
+    // This is an event that was synthesized within InputDispatcher; either being derived
+    // from an inbound event (e.g. a split motion event), or synthesized completely
+    // (e.g. a CANCEL event generated when the inbound stream is not canceled).
+    SYNTHESIZED,
+
+    ftl_last = SYNTHESIZED,
+};
+
+/**
  * A representation of an Android KeyEvent used by the tracing backend.
  */
 struct TracedKeyEvent {
@@ -35,7 +50,7 @@
     uint32_t policyFlags;
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t keyCode;
     int32_t scanCode;
@@ -43,6 +58,7 @@
     nsecs_t downTime;
     int32_t flags;
     int32_t repeatCount;
+    EventType eventType;
 };
 
 /**
@@ -54,7 +70,7 @@
     uint32_t policyFlags;
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId;
     int32_t action;
     int32_t actionButton;
     int32_t flags;
@@ -69,11 +85,38 @@
     nsecs_t downTime;
     std::vector<PointerProperties> pointerProperties;
     std::vector<PointerCoords> pointerCoords;
+    EventType eventType;
 };
 
 /** A representation of a traced input event. */
 using TracedEvent = std::variant<TracedKeyEvent, TracedMotionEvent>;
 
+/** Additional information about an input event being traced. */
+struct TracedEventMetadata {
+    // True if the event is targeting at least one secure window.
+    bool isSecure;
+    // The list of possible UIDs that this event could be targeting.
+    std::set<gui::Uid> targets;
+    // True if the there was an active input method connection while this event was processed.
+    bool isImeConnectionActive;
+    // The timestamp for when the dispatching decisions were made for the event by the system.
+    nsecs_t processingTimestamp;
+};
+
+/** Additional information about an input event being dispatched to a window. */
+struct WindowDispatchArgs {
+    TracedEvent eventEntry;
+    nsecs_t deliveryTime;
+    int32_t resolvedFlags;
+    gui::Uid targetUid;
+    int64_t vsyncId;
+    int32_t windowId;
+    ui::Transform transform;
+    ui::Transform rawTransform;
+    std::array<uint8_t, 32> hmac;
+    int32_t resolvedKeyRepeatCount;
+};
+
 /**
  * An interface for the tracing backend, used for setting a custom backend for testing.
  */
@@ -82,24 +125,13 @@
     virtual ~InputTracingBackendInterface() = default;
 
     /** Trace a KeyEvent. */
-    virtual void traceKeyEvent(const TracedKeyEvent&) = 0;
+    virtual void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) = 0;
 
     /** Trace a MotionEvent. */
-    virtual void traceMotionEvent(const TracedMotionEvent&) = 0;
+    virtual void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) = 0;
 
     /** Trace an event being sent to a window. */
-    struct WindowDispatchArgs {
-        TracedEvent eventEntry;
-        nsecs_t deliveryTime;
-        int32_t resolvedFlags;
-        gui::Uid targetUid;
-        int64_t vsyncId;
-        int32_t windowId;
-        ui::Transform transform;
-        ui::Transform rawTransform;
-        std::array<uint8_t, 32> hmac;
-    };
-    virtual void traceWindowDispatch(const WindowDispatchArgs&) = 0;
+    virtual void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) = 0;
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 6fcc015..77b5c2e 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -21,9 +21,12 @@
 #include "AndroidInputEventProtoConverter.h"
 
 #include <android-base/logging.h>
+#include <binder/IServiceManager.h>
 #include <perfetto/trace/android/android_input_event.pbzero.h>
 #include <perfetto/trace/android/winscope_extensions.pbzero.h>
 #include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String16.h>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -31,29 +34,175 @@
 
 constexpr auto INPUT_EVENT_TRACE_DATA_SOURCE_NAME = "android.input.inputevent";
 
+bool isPermanentlyAllowed(gui::Uid uid) {
+    switch (uid.val()) {
+        case AID_SYSTEM:
+        case AID_SHELL:
+        case AID_ROOT:
+            return true;
+        default:
+            return false;
+    }
+}
+
+sp<content::pm::IPackageManagerNative> getPackageManager() {
+    sp<IServiceManager> serviceManager = defaultServiceManager();
+    if (!serviceManager) {
+        LOG(ERROR) << __func__ << ": unable to access native ServiceManager";
+        return nullptr;
+    }
+
+    sp<IBinder> binder = serviceManager->waitForService(String16("package_native"));
+    auto packageManager = interface_cast<content::pm::IPackageManagerNative>(binder);
+    if (!packageManager) {
+        LOG(ERROR) << ": unable to access native PackageManager";
+        return nullptr;
+    }
+    return packageManager;
+}
+
+gui::Uid getPackageUid(const sp<content::pm::IPackageManagerNative>& pm,
+                       const std::string& package) {
+    int32_t outUid = -1;
+    if (auto status = pm->getPackageUid(package, /*flags=*/0, AID_SYSTEM, &outUid);
+        !status.isOk()) {
+        LOG(INFO) << "Failed to get package UID from native package manager for package '"
+                  << package << "': " << status;
+        return gui::Uid::INVALID;
+    }
+    return gui::Uid{static_cast<uid_t>(outUid)};
+}
+
 } // namespace
 
 // --- PerfettoBackend::InputEventDataSource ---
 
-void PerfettoBackend::InputEventDataSource::OnStart(const perfetto::DataSourceBase::StartArgs&) {
-    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+PerfettoBackend::InputEventDataSource::InputEventDataSource() : mInstanceId(sNextInstanceId++) {}
+
+void PerfettoBackend::InputEventDataSource::OnSetup(const InputEventDataSource::SetupArgs& args) {
+    LOG(INFO) << "Setting up perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+    const auto rawConfig = args.config->android_input_event_config_raw();
+    auto protoConfig = perfetto::protos::pbzero::AndroidInputEventConfig::Decoder{rawConfig};
+
+    mConfig = AndroidInputEventProtoConverter::parseConfig(protoConfig);
 }
 
-void PerfettoBackend::InputEventDataSource::OnStop(const perfetto::DataSourceBase::StopArgs&) {
-    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME;
+void PerfettoBackend::InputEventDataSource::OnStart(const InputEventDataSource::StartArgs&) {
+    LOG(INFO) << "Starting perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
+}
+
+void PerfettoBackend::InputEventDataSource::OnStop(const InputEventDataSource::StopArgs&) {
+    LOG(INFO) << "Stopping perfetto trace for: " << INPUT_EVENT_TRACE_DATA_SOURCE_NAME
+              << ", instanceId: " << mInstanceId;
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { ctx.Flush(); });
 }
 
+void PerfettoBackend::InputEventDataSource::initializeUidMap() {
+    if (mUidMap.has_value()) {
+        return;
+    }
+
+    mUidMap = {{}};
+    auto packageManager = PerfettoBackend::sPackageManagerProvider();
+    if (!packageManager) {
+        LOG(ERROR) << "Failed to initialize UID map: Could not get native package manager";
+        return;
+    }
+
+    for (const auto& rule : mConfig.rules) {
+        for (const auto& package : rule.matchAllPackages) {
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
+        }
+        for (const auto& package : rule.matchAnyPackages) {
+            mUidMap->emplace(package, getPackageUid(packageManager, package));
+        }
+    }
+}
+
+bool PerfettoBackend::InputEventDataSource::shouldIgnoreTracedInputEvent(
+        const EventType& type) const {
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS)) {
+        // Ignore all input events.
+        return true;
+    }
+    if (!getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH) &&
+        type != EventType::INBOUND) {
+        // When window dispatch tracing is disabled, ignore any events that are not inbound events.
+        return true;
+    }
+    return false;
+}
+
+TraceLevel PerfettoBackend::InputEventDataSource::resolveTraceLevel(
+        const TracedEventMetadata& metadata) const {
+    // Check for matches with the rules in the order that they are defined.
+    for (const auto& rule : mConfig.rules) {
+        if (ruleMatches(rule, metadata)) {
+            return rule.level;
+        }
+    }
+    // The event is not traced if it matched zero rules.
+    return TraceLevel::TRACE_LEVEL_NONE;
+}
+
+bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule,
+                                                        const TracedEventMetadata& metadata) const {
+    // By default, a rule will match all events. Return early if the rule does not match.
+
+    // Match the event if it is directed to a secure window.
+    if (rule.matchSecure.has_value() && *rule.matchSecure != metadata.isSecure) {
+        return false;
+    }
+
+    // Match the event if it was processed while there was an active InputMethod connection.
+    if (rule.matchImeConnectionActive.has_value() &&
+        *rule.matchImeConnectionActive != metadata.isImeConnectionActive) {
+        return false;
+    }
+
+    // Match the event if all of its target packages are explicitly allowed in the "match all" list.
+    if (!rule.matchAllPackages.empty() &&
+        !std::all_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
+            return isPermanentlyAllowed(uid) ||
+                    std::any_of(rule.matchAllPackages.begin(), rule.matchAllPackages.end(),
+                                [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // Match the event if any of its target packages are allowed in the "match any" list.
+    if (!rule.matchAnyPackages.empty() &&
+        !std::any_of(metadata.targets.begin(), metadata.targets.end(), [&](const auto& uid) {
+            return std::any_of(rule.matchAnyPackages.begin(), rule.matchAnyPackages.end(),
+                               [&](const auto& pkg) { return uid == mUidMap->at(pkg); });
+        })) {
+        return false;
+    }
+
+    // The event matches all matchers specified in the rule.
+    return true;
+}
+
 // --- PerfettoBackend ---
 
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
+std::function<sp<content::pm::IPackageManagerNative>()> PerfettoBackend::sPackageManagerProvider{
+        &getPackageManager};
+
 std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
 
+std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
+
 PerfettoBackend::PerfettoBackend() {
     // Use a once-flag to ensure that the data source is only registered once per boot, since
     // we never unregister the InputEventDataSource.
     std::call_once(sDataSourceRegistrationFlag, []() {
         perfetto::TracingInitArgs args;
-        args.backends = perfetto::kSystemBackend;
+        args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend
+                                                    : perfetto::kSystemBackend;
         perfetto::Tracing::Initialize(args);
 
         // Register our custom data source for input event tracing.
@@ -65,37 +214,89 @@
     });
 }
 
-void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) {
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event,
+                                       const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
+        tracePacket->set_timestamp(metadata.processingTimestamp);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
         auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
                 tracePacket->set_winscope_extensions());
         auto* inputEvent = winscopeExtensions->set_android_input_event();
-        auto* dispatchMotion = inputEvent->set_dispatcher_motion_event();
-        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion);
+        auto* dispatchMotion = isRedacted ? inputEvent->set_dispatcher_motion_event_redacted()
+                                          : inputEvent->set_dispatcher_motion_event();
+        AndroidInputEventProtoConverter::toProtoMotionEvent(event, *dispatchMotion, isRedacted);
     });
 }
 
-void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) {
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event,
+                                    const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
+        tracePacket->set_timestamp(metadata.processingTimestamp);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
         auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
                 tracePacket->set_winscope_extensions());
         auto* inputEvent = winscopeExtensions->set_android_input_event();
-        auto* dispatchKey = inputEvent->set_dispatcher_key_event();
-        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey);
+        auto* dispatchKey = isRedacted ? inputEvent->set_dispatcher_key_event_redacted()
+                                       : inputEvent->set_dispatcher_key_event();
+        AndroidInputEventProtoConverter::toProtoKeyEvent(event, *dispatchKey, isRedacted);
     });
 }
 
-void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+                                          const TracedEventMetadata& metadata) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
+        auto dataSource = ctx.GetDataSourceLocked();
+        if (!dataSource.valid()) {
+            return;
+        }
+        dataSource->initializeUidMap();
+        if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) {
+            return;
+        }
+        const TraceLevel traceLevel = dataSource->resolveTraceLevel(metadata);
+        if (traceLevel == TraceLevel::TRACE_LEVEL_NONE) {
+            return;
+        }
+        const bool isRedacted = traceLevel == TraceLevel::TRACE_LEVEL_REDACTED;
         auto tracePacket = ctx.NewTracePacket();
+        tracePacket->set_timestamp(dispatchArgs.deliveryTime);
+        tracePacket->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
         auto* winscopeExtensions = static_cast<perfetto::protos::pbzero::WinscopeExtensionsImpl*>(
                 tracePacket->set_winscope_extensions());
-        auto* inputEventProto = winscopeExtensions->set_android_input_event();
-        auto* dispatchEventProto = inputEventProto->set_dispatcher_window_dispatch_event();
-        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs,
-                                                                    *dispatchEventProto);
+        auto* inputEvent = winscopeExtensions->set_android_input_event();
+        auto* dispatchEvent = isRedacted
+                ? inputEvent->set_dispatcher_window_dispatch_event_redacted()
+                : inputEvent->set_dispatcher_window_dispatch_event();
+        AndroidInputEventProtoConverter::toProtoWindowDispatchEvent(dispatchArgs, *dispatchEvent,
+                                                                    isRedacted);
     });
 }
 
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index fefcfb3..d0bab06 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -18,8 +18,13 @@
 
 #include "InputTracingBackendInterface.h"
 
+#include "InputTracingPerfettoBackendConfig.h"
+
+#include <android/content/pm/IPackageManagerNative.h>
+#include <ftl/flags.h>
 #include <perfetto/tracing.h>
 #include <mutex>
+#include <set>
 
 namespace android::inputdispatcher::trace::impl {
 
@@ -45,22 +50,44 @@
  */
 class PerfettoBackend : public InputTracingBackendInterface {
 public:
-    PerfettoBackend();
+    static bool sUseInProcessBackendForTest;
+    static std::function<sp<content::pm::IPackageManagerNative>()> sPackageManagerProvider;
+
+    explicit PerfettoBackend();
     ~PerfettoBackend() override = default;
 
-    void traceKeyEvent(const TracedKeyEvent&) override;
-    void traceMotionEvent(const TracedMotionEvent&) override;
-    void traceWindowDispatch(const WindowDispatchArgs&) override;
-
-    class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
-    public:
-        void OnSetup(const SetupArgs&) override {}
-        void OnStart(const StartArgs&) override;
-        void OnStop(const StopArgs&) override;
-    };
+    void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+    void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
 
 private:
+    // Implementation of the perfetto data source.
+    // Each instance of the InputEventDataSource represents a different tracing session.
+    // Its lifecycle is controlled by perfetto.
+    class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
+    public:
+        explicit InputEventDataSource();
+
+        void OnSetup(const SetupArgs&) override;
+        void OnStart(const StartArgs&) override;
+        void OnStop(const StopArgs&) override;
+
+        void initializeUidMap();
+        bool shouldIgnoreTracedInputEvent(const EventType&) const;
+        inline ftl::Flags<TraceFlag> getFlags() const { return mConfig.flags; }
+        TraceLevel resolveTraceLevel(const TracedEventMetadata&) const;
+
+    private:
+        const int32_t mInstanceId;
+        TraceConfig mConfig;
+
+        bool ruleMatches(const TraceRule&, const TracedEventMetadata&) const;
+
+        std::optional<std::map<std::string, gui::Uid>> mUidMap;
+    };
+
     static std::once_flag sDataSourceRegistrationFlag;
+    static std::atomic<int32_t> sNextInstanceId;
 };
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
new file mode 100644
index 0000000..536e32b
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackendConfig.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <ftl/enum.h>
+#include <ftl/flags.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/** Flags representing the configurations that are enabled in the trace. */
+enum class TraceFlag : uint32_t {
+    // Trace details about input events processed by InputDispatcher.
+    TRACE_DISPATCHER_INPUT_EVENTS = 0x1,
+    // Trace details about an event being sent to a window by InputDispatcher.
+    TRACE_DISPATCHER_WINDOW_DISPATCH = 0x2,
+
+    ftl_last = TRACE_DISPATCHER_WINDOW_DISPATCH,
+};
+
+/** Representation of AndroidInputEventConfig::TraceLevel. */
+using TraceLevel = perfetto::protos::pbzero::AndroidInputEventConfig::TraceLevel;
+
+/** Representation of AndroidInputEventConfig::TraceRule. */
+struct TraceRule {
+    TraceLevel level;
+
+    std::vector<std::string> matchAllPackages;
+    std::vector<std::string> matchAnyPackages;
+    std::optional<bool> matchSecure;
+    std::optional<bool> matchImeConnectionActive;
+};
+
+/**
+ * A complete configuration for a tracing session.
+ *
+ * The trace rules are applied as documented in the perfetto config:
+ *   /external/perfetto/protos/perfetto/config/android/android_input_event_config.proto
+ */
+struct TraceConfig {
+    ftl::Flags<TraceFlag> flags;
+    std::vector<TraceRule> rules;
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 25bc227..3c3c15a 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -38,10 +38,10 @@
 
 template <typename Backend>
 ThreadedBackend<Backend>::ThreadedBackend(Backend&& innerBackend)
-      : mTracerThread(
+      : mBackend(std::move(innerBackend)),
+        mTracerThread(
                 "InputTracer", [this]() { threadLoop(); },
-                [this]() { mThreadWakeCondition.notify_all(); }),
-        mBackend(std::move(innerBackend)) {}
+                [this]() { mThreadWakeCondition.notify_all(); }) {}
 
 template <typename Backend>
 ThreadedBackend<Backend>::~ThreadedBackend() {
@@ -53,23 +53,29 @@
 }
 
 template <typename Backend>
-void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event) {
+void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event,
+                                                const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
-    mQueue.emplace_back(event);
+    mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
 template <typename Backend>
-void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event) {
+void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event,
+                                             const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
-    mQueue.emplace_back(event);
+    mQueue.emplace_back(event, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
 template <typename Backend>
-void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs,
+                                                   const TracedEventMetadata& metadata) {
     std::scoped_lock lock(mLock);
-    mQueue.emplace_back(dispatchArgs);
+    mQueue.emplace_back(dispatchArgs, metadata);
+    setIdleStatus(false);
     mThreadWakeCondition.notify_all();
 }
 
@@ -81,10 +87,15 @@
         std::unique_lock lock(mLock);
         base::ScopedLockAssertion assumeLocked(mLock);
 
+        if (mQueue.empty()) {
+            setIdleStatus(true);
+        }
+
         // Wait until we need to process more events or exit.
         mThreadWakeCondition.wait(lock,
                                   [&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); });
         if (mThreadExit) {
+            setIdleStatus(true);
             return;
         }
 
@@ -93,17 +104,49 @@
 
     // Trace the events into the backend without holding the lock to reduce the amount of
     // work performed in the critical section.
-    for (const auto& entry : entries) {
-        std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend.traceMotionEvent(e); },
-                           [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e); },
+    for (const auto& [entry, traceArgs] : entries) {
+        std::visit(Visitor{[&](const TracedMotionEvent& e) {
+                               mBackend.traceMotionEvent(e, traceArgs);
+                           },
+                           [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e, traceArgs); },
                            [&](const WindowDispatchArgs& args) {
-                               mBackend.traceWindowDispatch(args);
+                               mBackend.traceWindowDispatch(args, traceArgs);
                            }},
                    entry);
     }
     entries.clear();
 }
 
+template <typename Backend>
+std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() {
+    std::scoped_lock lock(mLock);
+    if (!mIdleWaiter) {
+        mIdleWaiter = std::make_shared<IdleWaiter>();
+    }
+
+    // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend
+    // beyond this threaded backend object.
+    return [idleWaiter = mIdleWaiter]() {
+        std::unique_lock idleLock(idleWaiter->idleLock);
+        base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock);
+        idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) {
+            return idleWaiter->isIdle;
+        });
+    };
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) {
+    if (!mIdleWaiter) {
+        return;
+    }
+    std::scoped_lock idleLock(mIdleWaiter->idleLock);
+    mIdleWaiter->isIdle = isIdle;
+    if (isIdle) {
+        mIdleWaiter->threadIdleCondition.notify_all();
+    }
+}
+
 // Explicit template instantiation for the PerfettoBackend.
 template class ThreadedBackend<PerfettoBackend>;
 
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
index 5776cf9..52a84c4 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -38,22 +38,38 @@
     ThreadedBackend(Backend&& innerBackend);
     ~ThreadedBackend() override;
 
-    void traceKeyEvent(const TracedKeyEvent&) override;
-    void traceMotionEvent(const TracedMotionEvent&) override;
-    void traceWindowDispatch(const WindowDispatchArgs&) override;
+    void traceKeyEvent(const TracedKeyEvent&, const TracedEventMetadata&) override;
+    void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override;
+
+    /** Returns a function that, when called, will block until the tracing thread is idle. */
+    std::function<void()> getIdleWaiterForTesting();
 
 private:
     std::mutex mLock;
-    InputThread mTracerThread;
     bool mThreadExit GUARDED_BY(mLock){false};
     std::condition_variable mThreadWakeCondition;
     Backend mBackend;
-    using TraceEntry = std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>;
+    using TraceEntry =
+            std::pair<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>,
+                      TracedEventMetadata>;
     std::vector<TraceEntry> mQueue GUARDED_BY(mLock);
 
-    using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
+    struct IdleWaiter {
+        std::mutex idleLock;
+        std::condition_variable threadIdleCondition;
+        bool isIdle GUARDED_BY(idleLock){false};
+    };
+    // The lazy-initialized object used to wait for the tracing thread to idle.
+    std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock);
+
+    // InputThread stops when its destructor is called. Initialize it last so that it is the
+    // first thing to be destructed. This will guarantee the thread will not access other
+    // members that have already been destructed.
+    InputThread mTracerThread;
 
     void threadLoop();
+    void setIdleStatus(bool isIdle) REQUIRES(mLock);
 };
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index 0b7f7c2..d8a9afa 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -38,7 +38,6 @@
     virtual ~InputListenerInterface() { }
 
     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;
@@ -60,7 +59,6 @@
     explicit QueuedInputListener(InputListenerInterface& innerListener);
 
     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;
@@ -84,7 +82,6 @@
     explicit TracedInputListener(const char* name, InputListenerInterface& innerListener);
 
     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;
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 79c8a4b..2f6c6d7 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -34,7 +34,9 @@
 #include <vector>
 
 #include "PointerControllerInterface.h"
+#include "TouchpadHardwareState.h"
 #include "VibrationElement.h"
+#include "include/gestures.h"
 
 // Maximum supported size of a vibration pattern.
 // Must be at least 2.
@@ -61,9 +63,6 @@
         // The display size or orientation changed.
         DISPLAY_INFO = 1u << 2,
 
-        // The visible touches option changed.
-        SHOW_TOUCHES = 1u << 3,
-
         // The keyboard layouts must be reloaded.
         KEYBOARD_LAYOUTS = 1u << 4,
 
@@ -94,6 +93,9 @@
         // The touchpad settings changed.
         TOUCHPAD_SETTINGS = 1u << 13,
 
+        // The key remapping has changed.
+        KEY_REMAPPING = 1u << 14,
+
         // All devices must be reopened.
         MUST_REOPEN = 1u << 31,
     };
@@ -109,11 +111,15 @@
 
     // The associations between input ports and display ports.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, uint8_t> portAssociations;
+    std::unordered_map<std::string, uint8_t> inputPortToDisplayPortAssociations;
 
-    // The associations between input device physical port locations and display unique ids.
+    // The associations between input device ports and display unique ids.
     // Used to determine which DisplayViewport should be tied to which InputDevice.
-    std::unordered_map<std::string, std::string> uniqueIdAssociations;
+    std::unordered_map<std::string, std::string> inputPortToDisplayUniqueIdAssociations;
+
+    // The associations between input device descriptor and display unique ids.
+    // Used to determine which DisplayViewport should be tied to which InputDevice.
+    std::unordered_map<std::string, std::string> inputDeviceDescriptorToDisplayUniqueIdAssociations;
 
     // The associations between input device ports device types.
     // This is used to determine which device type and source should be tied to which InputDevice.
@@ -124,7 +130,7 @@
     std::unordered_map<std::string, KeyboardLayoutInfo> keyboardLayoutAssociations;
 
     // The suggested display ID to show the cursor.
-    int32_t defaultPointerDisplayId;
+    ui::LogicalDisplayId defaultPointerDisplayId;
 
     // The mouse pointer speed, as a number from -7 (slowest) to 7 (fastest).
     //
@@ -134,7 +140,7 @@
     // Displays on which an acceleration curve shouldn't be applied for pointer movements from mice.
     //
     // Currently only used when the enable_new_mouse_pointer_ballistics flag is enabled.
-    std::set<int32_t> displaysWithMousePointerAccelerationDisabled;
+    std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled;
 
     // Velocity control parameters for mouse pointer movements.
     //
@@ -210,9 +216,6 @@
     // will cover this portion of the display diagonal.
     float pointerGestureZoomSpeedRatio;
 
-    // True to show the location of touches on the touch screen as spots.
-    bool showTouches;
-
     // The latest request to enable or disable Pointer Capture.
     PointerCaptureRequest pointerCaptureRequest;
 
@@ -229,6 +232,9 @@
     // True to enable tap dragging on touchpads.
     bool touchpadTapDraggingEnabled;
 
+    // True if hardware state update notifications should be sent to the policy.
+    bool shouldNotifyTouchpadHardwareState;
+
     // True to enable a zone on the right-hand side of touchpads where clicks will be turned into
     // context (a.k.a. "right") clicks.
     bool touchpadRightClickZoneEnabled;
@@ -243,8 +249,12 @@
     // True if a pointer icon should be shown for direct stylus pointers.
     bool stylusPointerIconEnabled;
 
+    // Keycodes to be remapped.
+    std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
+            defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
             mousePointerSpeed(0),
             displaysWithMousePointerAccelerationDisabled(),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -264,12 +274,12 @@
             pointerGestureSwipeMaxWidthRatio(0.25f),
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
-            showTouches(false),
             pointerCaptureRequest(),
             touchpadPointerSpeed(0),
             touchpadNaturalScrollingEnabled(true),
             touchpadTapToClickEnabled(true),
             touchpadTapDraggingEnabled(false),
+            shouldNotifyTouchpadHardwareState(false),
             touchpadRightClickZoneEnabled(false),
             stylusButtonMotionEventsEnabled(true),
             stylusPointerIconEnabled(false) {}
@@ -278,7 +288,7 @@
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
             const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t physicalPort) const;
-    std::optional<DisplayViewport> getDisplayViewportById(int32_t displayId) const;
+    std::optional<DisplayViewport> getDisplayViewportById(ui::LogicalDisplayId displayId) const;
     void setDisplayViewports(const std::vector<DisplayViewport>& viewports);
 
     void dump(std::string& dump) const;
@@ -312,9 +322,6 @@
     /* 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;
 
@@ -332,9 +339,6 @@
     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 */
@@ -368,8 +372,10 @@
 
     virtual std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) = 0;
 
+    virtual std::optional<HardwareProperties> getTouchpadHardwareProperties(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;
+    virtual bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) = 0;
 
     /* Enable sensor in input reader mapper. */
     virtual bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
@@ -396,6 +402,15 @@
 
     /* Sysfs node change reported. Recreate device if required to incorporate the new sysfs nodes */
     virtual void sysfsNodeChanged(const std::string& sysfsNodePath) = 0;
+
+    /* Get the ID of the InputDevice that was used most recently.
+     *
+     * Returns ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID if no device has been used since boot.
+     */
+    virtual DeviceId getLastUsedInputDeviceId() = 0;
+
+    /* Notifies that mouse cursor faded due to typing. */
+    virtual void notifyMouseCursorFadedOnTyping() = 0;
 };
 
 // --- TouchAffineTransformation ---
@@ -445,15 +460,18 @@
     /* Gets the input reader configuration. */
     virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) = 0;
 
-    /* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
-    virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t deviceId) = 0;
-
     /* Notifies the input reader policy that some input devices have changed
      * and provides information about all current input devices.
      */
     virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) = 0;
 
+    /* Sends the hardware state of a connected touchpad */
+    virtual void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                             int32_t deviceId) = 0;
+
+    /* Sends the Info of gestures that happen on the touchpad. */
+    virtual void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) = 0;
+
     /* Gets the keyboard layout for a particular input device. */
     virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
@@ -478,7 +496,7 @@
      * be used as the range of possible values for pointing devices, like mice and touchpads.
      */
     virtual std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
+            ui::LogicalDisplayId associatedDisplayId = ui::LogicalDisplayId::INVALID) = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h
index 5e75027..fcd913d 100644
--- a/services/inputflinger/include/InputThread.h
+++ b/services/inputflinger/include/InputThread.h
@@ -38,6 +38,7 @@
     std::string mName;
     std::function<void()> mThreadWake;
     sp<Thread> mThread;
+    bool applyInputEventProfile();
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
index 736b1e0..14487fe 100644
--- a/services/inputflinger/include/NotifyArgs.h
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -39,21 +39,6 @@
     NotifyInputDevicesChangedArgs& operator=(const NotifyInputDevicesChangedArgs&) = default;
 };
 
-/* Describes a configuration change event. */
-struct NotifyConfigurationChangedArgs {
-    int32_t id;
-    nsecs_t eventTime;
-
-    inline NotifyConfigurationChangedArgs() {}
-
-    NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime);
-
-    bool operator==(const NotifyConfigurationChangedArgs& rhs) const = default;
-
-    NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other) = default;
-    NotifyConfigurationChangedArgs& operator=(const NotifyConfigurationChangedArgs&) = default;
-};
-
 /* Describes a key event. */
 struct NotifyKeyArgs {
     int32_t id;
@@ -61,7 +46,7 @@
 
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     uint32_t policyFlags;
     int32_t action;
     int32_t flags;
@@ -74,9 +59,9 @@
     inline NotifyKeyArgs() {}
 
     NotifyKeyArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                  uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
-                  int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
-                  nsecs_t downTime);
+                  uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
+                  int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+                  int32_t metaState, nsecs_t downTime);
 
     bool operator==(const NotifyKeyArgs& rhs) const = default;
 
@@ -91,7 +76,7 @@
 
     int32_t deviceId;
     uint32_t source;
-    int32_t displayId;
+    ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
     uint32_t policyFlags;
     int32_t action;
     int32_t actionButton;
@@ -123,12 +108,12 @@
     inline NotifyMotionArgs() {}
 
     NotifyMotionArgs(int32_t id, nsecs_t eventTime, nsecs_t readTime, int32_t deviceId,
-                     uint32_t source, int32_t displayId, uint32_t policyFlags, int32_t action,
-                     int32_t actionButton, int32_t flags, int32_t metaState, int32_t buttonState,
-                     MotionClassification classification, int32_t edgeFlags, uint32_t pointerCount,
-                     const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
-                     float xPrecision, float yPrecision, float xCursorPosition,
-                     float yCursorPosition, nsecs_t downTime,
+                     uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
+                     int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
+                     int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
+                     uint32_t pointerCount, const PointerProperties* pointerProperties,
+                     const PointerCoords* pointerCoords, float xPrecision, float yPrecision,
+                     float xCursorPosition, float yCursorPosition, nsecs_t downTime,
                      const std::vector<TouchVideoFrame>& videoFrames);
 
     NotifyMotionArgs(const NotifyMotionArgs& other) = default;
@@ -234,8 +219,8 @@
 };
 
 using NotifyArgs =
-        std::variant<NotifyInputDevicesChangedArgs, NotifyConfigurationChangedArgs, NotifyKeyArgs,
-                     NotifyMotionArgs, NotifySensorArgs, NotifySwitchArgs, NotifyDeviceResetArgs,
+        std::variant<NotifyInputDevicesChangedArgs, NotifyKeyArgs, NotifyMotionArgs,
+                     NotifySensorArgs, NotifySwitchArgs, NotifyDeviceResetArgs,
                      NotifyPointerCaptureChangedArgs, NotifyVibratorStateArgs>;
 
 const char* toString(const NotifyArgs& args);
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index e4363a4..5b94d57 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -19,9 +19,9 @@
 #include <NotifyArgs.h>
 #include <android/input.h>
 #include <attestation/HmacKeyManager.h>
-#include <gui/constants.h>
 #include <input/Input.h>
 #include <input/InputEventBuilders.h>
+#include <input/Keyboard.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -30,8 +30,11 @@
 
 class MotionArgsBuilder {
 public:
-    MotionArgsBuilder(int32_t action, int32_t source) {
+    MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            addFlag(AMOTION_EVENT_FLAG_CANCELED);
+        }
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
         mDownTime = mEventTime;
@@ -52,7 +55,7 @@
         return *this;
     }
 
-    MotionArgsBuilder& displayId(int32_t displayId) {
+    MotionArgsBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -97,7 +100,7 @@
         return *this;
     }
 
-    NotifyMotionArgs build() {
+    NotifyMotionArgs build() const {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
         for (const PointerBuilder& pointer : mPointers) {
@@ -106,17 +109,17 @@
         }
 
         // Set mouse cursor position for the most common cases to avoid boilerplate.
+        float resolvedCursorX = mRawXCursorPosition;
+        float resolvedCursorY = mRawYCursorPosition;
         if (mSource == AINPUT_SOURCE_MOUSE &&
-            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition)) {
-            mRawXCursorPosition = pointerCoords[0].getX();
-            mRawYCursorPosition = pointerCoords[0].getY();
+            !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) &&
+            BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) &&
+            BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) {
+            resolvedCursorX = pointerCoords[0].getX();
+            resolvedCursorY = pointerCoords[0].getY();
         }
 
-        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
-            addFlag(AMOTION_EVENT_FLAG_CANCELED);
-        }
-
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -135,19 +138,20 @@
                 pointerCoords.data(),
                 /*xPrecision=*/0,
                 /*yPrecision=*/0,
-                mRawXCursorPosition,
-                mRawYCursorPosition,
+                resolvedCursorX,
+                resolvedCursorY,
                 mDownTime,
                 /*videoFrames=*/{}};
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId{DEFAULT_DEVICE_ID};
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mActionButton{0};
     int32_t mButtonState{0};
@@ -161,7 +165,7 @@
 
 class KeyArgsBuilder {
 public:
-    KeyArgsBuilder(int32_t action, int32_t source) {
+    KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) {
         mAction = action;
         mSource = source;
         mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -183,7 +187,7 @@
         return *this;
     }
 
-    KeyArgsBuilder& displayId(int32_t displayId) {
+    KeyArgsBuilder& displayId(ui::LogicalDisplayId displayId) {
         mDisplayId = displayId;
         return *this;
     }
@@ -203,8 +207,14 @@
         return *this;
     }
 
+    KeyArgsBuilder& metaState(int32_t metaState) {
+        mMetaState |= metaState;
+        mMetaState = normalizeMetaState(/*oldMetaState=*/mMetaState);
+        return *this;
+    }
+
     NotifyKeyArgs build() const {
-        return {InputEvent::nextId(),
+        return {mEventId,
                 mEventTime,
                 /*readTime=*/mEventTime,
                 mDeviceId,
@@ -220,12 +230,13 @@
     }
 
 private:
+    const int32_t mEventId;
     int32_t mAction;
     int32_t mDeviceId = DEFAULT_DEVICE_ID;
     uint32_t mSource;
     nsecs_t mDownTime;
     nsecs_t mEventTime;
-    int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+    ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
     uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
     int32_t mFlags{0};
     int32_t mKeyCode{AKEYCODE_UNKNOWN};
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 462aedc..e1f8fda 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -53,7 +53,14 @@
      * @param displayId The updated display on which the mouse cursor is shown
      * @param position The new position of the mouse cursor on the logical display
      */
-    virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
+    virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
+                                               const FloatPoint& position) = 0;
+
+    /* Returns true if any InputConnection is currently active. */
+    virtual bool isInputMethodConnectionActive() = 0;
+
+    /* Notifies that mouse cursor faded due to typing. */
+    virtual void notifyMouseCursorFadedOnTyping() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index c44486f..8f3d9ca 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -61,8 +61,6 @@
      * TODO(b/293587049): Refactor the PointerController class into different controller types.
      */
     enum class ControllerType {
-        // The PointerController that is responsible for drawing all icons.
-        LEGACY,
         // Represents a single mouse pointer.
         MOUSE,
         // Represents multiple touch spots.
@@ -74,10 +72,6 @@
     /* Dumps the state of the pointer controller. */
     virtual std::string dump() = 0;
 
-    /* Gets the bounds of the region that the pointer can traverse.
-     * Returns true if the bounds are available. */
-    virtual std::optional<FloatRect> getBounds() const = 0;
-
     /* Move the pointer. */
     virtual void move(float deltaX, float deltaY) = 0;
 
@@ -127,13 +121,13 @@
      * pressed (not hovering).
      */
     virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                          BitSet32 spotIdBits, int32_t displayId) = 0;
+                          BitSet32 spotIdBits, ui::LogicalDisplayId displayId) = 0;
 
     /* Removes all spots. */
     virtual void clearSpots() = 0;
 
     /* Gets the id of the display where the pointer should be shown. */
-    virtual int32_t getDisplayId() const = 0;
+    virtual ui::LogicalDisplayId getDisplayId() const = 0;
 
     /* Sets the associated display of this pointer. Pointer should show on that display. */
     virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
@@ -143,6 +137,14 @@
 
     /* Sets the custom pointer icon for mice or styluses. */
     virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
+
+    /* Sets the flag to skip screenshot of the pointer indicators on the display for the specified
+     * displayId. This flag can only be reset with resetSkipScreenshotFlags()
+     */
+    virtual void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) = 0;
+
+    /* Resets the flag to skip screenshot of the pointer indicators for all displays. */
+    virtual void clearSkipScreenshotFlags() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/include/TouchpadHardwareState.h b/services/inputflinger/include/TouchpadHardwareState.h
new file mode 100644
index 0000000..a8dd44c
--- /dev/null
+++ b/services/inputflinger/include/TouchpadHardwareState.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2024 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 <include/gestures.h>
+#include <vector>
+
+namespace android {
+
+// A Gestures library HardwareState struct (from libchrome-gestures), but bundled
+// with a vector to contain its FingerStates, so you don't have to worry about where
+// that memory is allocated.
+struct SelfContainedHardwareState {
+    HardwareState state;
+    std::vector<FingerState> fingers;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index e76b648..b76e8c5 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -78,6 +78,7 @@
     name: "libinputreader_defaults",
     srcs: [":libinputreader_sources"],
     shared_libs: [
+        "android.companion.virtualdevice.flags-aconfig-cc",
         "libbase",
         "libcap",
         "libcrypto",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 3ca691e..0865eed 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -33,6 +33,8 @@
 #include <sys/sysmacros.h>
 #include <unistd.h>
 
+#include <android_companion_virtualdevice_flags.h>
+
 #define LOG_TAG "EventHub"
 
 // #define LOG_NDEBUG 0
@@ -68,6 +70,8 @@
 
 namespace android {
 
+namespace vd_flags = android::companion::virtualdevice::flags;
+
 using namespace ftl::flag_operators;
 
 static const char* DEVICE_INPUT_PATH = "/dev/input";
@@ -123,7 +127,8 @@
          {"multi_index", InputLightClass::MULTI_INDEX},
          {"multi_intensity", InputLightClass::MULTI_INTENSITY},
          {"max_brightness", InputLightClass::MAX_BRIGHTNESS},
-         {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT}};
+         {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT},
+         {"mic_mute", InputLightClass::KEYBOARD_MIC_MUTE}};
 
 // Mapping for input multicolor led class node names.
 // https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html
@@ -512,10 +517,10 @@
 
 // --- RawAbsoluteAxisInfo ---
 
-std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) {
-    if (info.valid) {
-        out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat
-            << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution;
+std::ostream& operator<<(std::ostream& out, const std::optional<RawAbsoluteAxisInfo>& info) {
+    if (info) {
+        out << "min=" << info->minValue << ", max=" << info->maxValue << ", flat=" << info->flat
+            << ", fuzz=" << info->fuzz << ", resolution=" << info->resolution;
     } else {
         out << "unknown range";
     }
@@ -644,7 +649,6 @@
             continue;
         }
         auto& [axisInfo, value] = absState[axis];
-        axisInfo.valid = true;
         axisInfo.minValue = info.minimum;
         axisInfo.maxValue = info.maximum;
         axisInfo.flat = info.flat;
@@ -884,7 +888,6 @@
       : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
         mNextDeviceId(1),
         mControllerNumbers(),
-        mNeedToSendFinishedDeviceScan(false),
         mNeedToReopenDevices(false),
         mNeedToScanDevices(true),
         mPendingEventCount(0),
@@ -997,26 +1000,23 @@
     return *device->configuration;
 }
 
-status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                       RawAbsoluteAxisInfo* outAxisInfo) const {
-    outAxisInfo->clear();
+std::optional<RawAbsoluteAxisInfo> EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
     // fd, because the info is populated once when the device is first opened, and it doesn't change
     // throughout the device lifecycle.
     auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outAxisInfo = it->second.info;
-    return OK;
+    return it->second.info;
 }
 
 bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -1129,22 +1129,20 @@
     return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
 }
 
-status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
-    *outValue = 0;
+std::optional<int32_t> EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     if (axis < 0 || axis > ABS_MAX) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     std::scoped_lock _l(mLock);
     const Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->hasValidFd()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
     const auto it = device->absState.find(axis);
     if (it == device->absState.end()) {
-        return NAME_NOT_FOUND;
+        return std::nullopt;
     }
-    *outValue = it->second.value;
-    return OK;
+    return it->second.value;
 }
 
 base::Result<std::vector<int32_t>> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -1179,7 +1177,8 @@
     return false;
 }
 
-void EventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
+void EventHub::setKeyRemapping(int32_t deviceId,
+                               const std::map<int32_t, int32_t>& keyRemapping) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
@@ -1187,7 +1186,7 @@
     }
     const std::shared_ptr<KeyCharacterMap> kcm = device->getKeyCharacterMap();
     if (kcm) {
-        kcm->addKeyRemapping(fromKeyCode, toKeyCode);
+        kcm->setKeyRemapping(keyRemapping);
     }
 }
 
@@ -1878,7 +1877,6 @@
                     .type = DEVICE_REMOVED,
             });
             it = mClosingDevices.erase(it);
-            mNeedToSendFinishedDeviceScan = true;
             if (events.size() == EVENT_BUFFER_SIZE) {
                 break;
             }
@@ -1887,7 +1885,6 @@
         if (mNeedToScanDevices) {
             mNeedToScanDevices = false;
             scanDevicesLocked();
-            mNeedToSendFinishedDeviceScan = true;
         }
 
         while (!mOpeningDevices.empty()) {
@@ -1916,18 +1913,6 @@
             if (!inserted) {
                 ALOGW("Device id %d exists, replaced.", device->id);
             }
-            mNeedToSendFinishedDeviceScan = true;
-            if (events.size() == EVENT_BUFFER_SIZE) {
-                break;
-            }
-        }
-
-        if (mNeedToSendFinishedDeviceScan) {
-            mNeedToSendFinishedDeviceScan = false;
-            events.push_back({
-                    .when = now,
-                    .type = FINISHED_DEVICE_SCAN,
-            });
             if (events.size() == EVENT_BUFFER_SIZE) {
                 break;
             }
@@ -2502,6 +2487,12 @@
         }
     }
 
+    // See if the device is a rotary encoder with a single scroll axis and nothing else.
+    if (vd_flags::virtual_rotary() && device->classes == ftl::Flags<InputDeviceClass>(0) &&
+        device->relBitmask.test(REL_WHEEL) && !device->relBitmask.test(REL_HWHEEL)) {
+        device->classes |= InputDeviceClass::ROTARY_ENCODER;
+    }
+
     // If the device isn't recognized as something we handle, don't monitor it.
     if (device->classes == ftl::Flags<InputDeviceClass>(0)) {
         ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(),
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 4d8ffb6..6185f1a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -77,11 +77,11 @@
 
         // If a device is associated with a specific display but there is no
         // associated DisplayViewport, don't enable the device.
-        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+        if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueIdByPort) &&
             !mAssociatedViewport) {
             const std::string desc = mAssociatedDisplayPort
                     ? "port " + std::to_string(*mAssociatedDisplayPort)
-                    : "uniqueId " + *mAssociatedDisplayUniqueId;
+                    : "uniqueId " + *mAssociatedDisplayUniqueIdByPort;
             ALOGW("Cannot enable input device %s because it is associated "
                   "with %s, but the corresponding viewport is not found",
                   getName().c_str(), desc.c_str());
@@ -124,9 +124,15 @@
     } else {
         dump += "<none>\n";
     }
-    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueId: ");
-    if (mAssociatedDisplayUniqueId) {
-        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueId->c_str());
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByPort: ");
+    if (mAssociatedDisplayUniqueIdByPort) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByPort->c_str());
+    } else {
+        dump += "<none>\n";
+    }
+    dump += StringPrintf(INDENT2 "AssociatedDisplayUniqueIdByDescriptor: ");
+    if (mAssociatedDisplayUniqueIdByDescriptor) {
+        dump += StringPrintf("%s\n", mAssociatedDisplayUniqueIdByDescriptor->c_str());
     } else {
         dump += "<none>\n";
     }
@@ -231,6 +237,12 @@
     mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
     mHasMic = mClasses.test(InputDeviceClass::MIC);
 
+    // Update keyboard type
+    if (mClasses.test(InputDeviceClass::KEYBOARD)) {
+        mContext->getKeyboardClassifier().notifyKeyboardChanged(mId, mIdentifier, mClasses.get());
+        mKeyboardType = mContext->getKeyboardClassifier().getKeyboardType(mId);
+    }
+
     using Change = InputReaderConfiguration::Change;
 
     if (!changes.any() || !isIgnored()) {
@@ -269,22 +281,42 @@
 
             // In most situations, no port or name will be specified.
             mAssociatedDisplayPort = std::nullopt;
-            mAssociatedDisplayUniqueId = std::nullopt;
+            mAssociatedDisplayUniqueIdByPort = std::nullopt;
             mAssociatedViewport = std::nullopt;
+            // Find the display port that corresponds to the current input device descriptor
+            const std::string& inputDeviceDescriptor = mIdentifier.descriptor;
+            if (!inputDeviceDescriptor.empty()) {
+                const std::unordered_map<std::string, uint8_t>& ports =
+                        readerConfig.inputPortToDisplayPortAssociations;
+                const auto& displayPort = ports.find(inputDeviceDescriptor);
+                if (displayPort != ports.end()) {
+                    mAssociatedDisplayPort = std::make_optional(displayPort->second);
+                } else {
+                    const std::unordered_map<std::string, std::string>&
+                            displayUniqueIdsByDescriptor =
+                                    readerConfig.inputDeviceDescriptorToDisplayUniqueIdAssociations;
+                    const auto& displayUniqueIdByDescriptor =
+                            displayUniqueIdsByDescriptor.find(inputDeviceDescriptor);
+                    if (displayUniqueIdByDescriptor != displayUniqueIdsByDescriptor.end()) {
+                        mAssociatedDisplayUniqueIdByDescriptor =
+                                displayUniqueIdByDescriptor->second;
+                    }
+                }
+            }
             // 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 =
-                        readerConfig.portAssociations;
+                        readerConfig.inputPortToDisplayPortAssociations;
                 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 =
-                            readerConfig.uniqueIdAssociations;
-                    const auto& displayUniqueId = displayUniqueIds.find(inputPort);
-                    if (displayUniqueId != displayUniqueIds.end()) {
-                        mAssociatedDisplayUniqueId = displayUniqueId->second;
+                    const std::unordered_map<std::string, std::string>& displayUniqueIdsByPort =
+                            readerConfig.inputPortToDisplayUniqueIdAssociations;
+                    const auto& displayUniqueIdByPort = displayUniqueIdsByPort.find(inputPort);
+                    if (displayUniqueIdByPort != displayUniqueIdsByPort.end()) {
+                        mAssociatedDisplayUniqueIdByPort = displayUniqueIdByPort->second;
                     }
                 }
             }
@@ -299,13 +331,21 @@
                           "but the corresponding viewport is not found.",
                           getName().c_str(), *mAssociatedDisplayPort);
                 }
-            } else if (mAssociatedDisplayUniqueId != std::nullopt) {
-                mAssociatedViewport =
-                        readerConfig.getDisplayViewportByUniqueId(*mAssociatedDisplayUniqueId);
+            } else if (mAssociatedDisplayUniqueIdByDescriptor != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByDescriptor);
                 if (!mAssociatedViewport) {
                     ALOGW("Input device %s should be associated with display %s but the "
                           "corresponding viewport cannot be found",
-                          getName().c_str(), mAssociatedDisplayUniqueId->c_str());
+                          getName().c_str(), mAssociatedDisplayUniqueIdByDescriptor->c_str());
+                }
+            } else if (mAssociatedDisplayUniqueIdByPort != std::nullopt) {
+                mAssociatedViewport = readerConfig.getDisplayViewportByUniqueId(
+                        *mAssociatedDisplayUniqueIdByPort);
+                if (!mAssociatedViewport) {
+                    ALOGW("Input device %s should be associated with display %s but the "
+                          "corresponding viewport cannot be found",
+                          getName().c_str(), mAssociatedDisplayUniqueIdByPort->c_str());
                 }
             }
 
@@ -325,6 +365,18 @@
             // so update the enabled state when there is a change in display info.
             out += updateEnableState(when, readerConfig, forceEnable);
         }
+
+        if (!changes.any() || changes.test(InputReaderConfiguration::Change::KEY_REMAPPING)) {
+            const bool isFullKeyboard =
+                    (mSources & AINPUT_SOURCE_KEYBOARD) == AINPUT_SOURCE_KEYBOARD &&
+                    mKeyboardType == KeyboardType::ALPHABETIC;
+            if (isFullKeyboard) {
+                for_each_subdevice([&readerConfig](auto& context) {
+                    context.setKeyRemapping(readerConfig.keyRemapping);
+                });
+                bumpGeneration();
+            }
+        }
     }
     return out;
 }
@@ -369,7 +421,7 @@
             mDropUntilNextSync = true;
         } else {
             for_each_mapper_in_subdevice(rawEvent->deviceId, [&](InputMapper& mapper) {
-                out += mapper.process(rawEvent);
+                out += mapper.process(*rawEvent);
             });
         }
         --count;
@@ -408,8 +460,10 @@
 InputDeviceInfo InputDevice::getDeviceInfo() {
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
-                             mHasMic, getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE),
-                             {mShouldSmoothScroll});
+                             mHasMic,
+                             getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID),
+                             {mShouldSmoothScroll}, isEnabled());
+    outDeviceInfo.setKeyboardType(static_cast<int32_t>(mKeyboardType));
 
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
@@ -482,13 +536,9 @@
 
     // 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;
     }
@@ -497,8 +547,8 @@
     }
 
     if (keyboardSource != 0) {
-        mappers.push_back(createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig,
-                                                                 keyboardSource, keyboardType));
+        mappers.push_back(
+                createInputMapper<KeyboardInputMapper>(contextPtr, readerConfig, keyboardSource));
     }
 
     // Cursor-like devices.
@@ -507,10 +557,7 @@
     }
 
     // Touchscreens and touchpad devices.
-    static const bool ENABLE_TOUCHPAD_GESTURES_LIBRARY =
-            sysprop::InputProperties::enable_touchpad_gestures_library().value_or(true);
-    if (ENABLE_TOUCHPAD_GESTURES_LIBRARY && classes.test(InputDeviceClass::TOUCHPAD) &&
-        classes.test(InputDeviceClass::TOUCH_MT)) {
+    if (classes.test(InputDeviceClass::TOUCHPAD) && classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<TouchpadInputMapper>(contextPtr, readerConfig));
     } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
         mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
@@ -654,12 +701,6 @@
     });
 }
 
-void InputDevice::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) {
-    for_each_subdevice([fromKeyCode, toKeyCode](auto& context) {
-        context.addKeyRemapping(fromKeyCode, toKeyCode);
-    });
-}
-
 void InputDevice::bumpGeneration() {
     mGeneration = mContext->bumpGeneration();
 }
@@ -668,14 +709,14 @@
     return NotifyDeviceResetArgs(mContext->getNextId(), when, mId);
 }
 
-std::optional<int32_t> InputDevice::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> InputDevice::getAssociatedDisplayId() {
     // Check if we had associated to the specific display.
     if (mAssociatedViewport) {
         return mAssociatedViewport->displayId;
     }
 
     // No associated display port, check if some InputMapper is associated.
-    return first_in_mappers<int32_t>(
+    return first_in_mappers<ui::LogicalDisplayId>(
             [](InputMapper& mapper) { return mapper.getAssociatedDisplayId(); });
 }
 
@@ -690,6 +731,15 @@
     return count;
 }
 
+std::optional<HardwareProperties> InputDevice::getTouchpadHardwareProperties() {
+    std::optional<HardwareProperties> result = first_in_mappers<HardwareProperties>(
+            [](InputMapper& mapper) -> std::optional<HardwareProperties> {
+                return mapper.getTouchpadHardwareProperties();
+            });
+
+    return result;
+}
+
 void InputDevice::updateLedState(bool reset) {
     for_each_mapper([reset](InputMapper& mapper) { mapper.updateLedState(reset); });
 }
@@ -698,6 +748,13 @@
     return mController ? std::make_optional(mController->getEventHubId()) : std::nullopt;
 }
 
+void InputDevice::setKeyboardType(KeyboardType keyboardType) {
+    if (mKeyboardType != keyboardType) {
+        mKeyboardType = keyboardType;
+        bumpGeneration();
+    }
+}
+
 InputDeviceContext::InputDeviceContext(InputDevice& device, int32_t eventHubId)
       : mDevice(device),
         mContext(device.getContext()),
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 9608210..e579390 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -33,11 +33,14 @@
 #include <utils/Thread.h>
 
 #include "InputDevice.h"
+#include "include/gestures.h"
 
 using android::base::StringPrintf;
 
 namespace android {
 
+namespace {
+
 /**
  * Determines if the identifiers passed are a sub-devices. Sub-devices are physical devices
  * that expose multiple input device paths such a keyboard that also has a touchpad input.
@@ -49,8 +52,8 @@
  *    inputs versus the same device plugged into multiple ports.
  */
 
-static bool isSubDevice(const InputDeviceIdentifier& identifier1,
-                        const InputDeviceIdentifier& identifier2) {
+bool isSubDevice(const InputDeviceIdentifier& identifier1,
+                 const InputDeviceIdentifier& identifier2) {
     return (identifier1.vendor == identifier2.vendor &&
             identifier1.product == identifier2.product && identifier1.bus == identifier2.bus &&
             identifier1.version == identifier2.version &&
@@ -58,7 +61,7 @@
             identifier1.location == identifier2.location);
 }
 
-static bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
+bool isStylusPointerGestureStart(const NotifyMotionArgs& motionArgs) {
     const auto actionMasked = MotionEvent::getActionMasked(motionArgs.action);
     if (actionMasked != AMOTION_EVENT_ACTION_HOVER_ENTER &&
         actionMasked != AMOTION_EVENT_ACTION_DOWN &&
@@ -69,6 +72,28 @@
     return isStylusToolType(motionArgs.pointerProperties[actionIndex].toolType);
 }
 
+bool isNewGestureStart(const NotifyMotionArgs& motion) {
+    return motion.action == AMOTION_EVENT_ACTION_DOWN ||
+            motion.action == AMOTION_EVENT_ACTION_HOVER_ENTER;
+}
+
+bool isNewGestureStart(const NotifyKeyArgs& key) {
+    return key.action == AKEY_EVENT_ACTION_DOWN;
+}
+
+// Return the event's device ID if it marks the start of a new gesture.
+std::optional<DeviceId> getDeviceIdOfNewGesture(const NotifyArgs& args) {
+    if (const auto* motion = std::get_if<NotifyMotionArgs>(&args); motion != nullptr) {
+        return isNewGestureStart(*motion) ? std::make_optional(motion->deviceId) : std::nullopt;
+    }
+    if (const auto* key = std::get_if<NotifyKeyArgs>(&args); key != nullptr) {
+        return isNewGestureStart(*key) ? std::make_optional(key->deviceId) : std::nullopt;
+    }
+    return std::nullopt;
+}
+
+} // namespace
+
 // --- InputReader ---
 
 InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
@@ -78,6 +103,7 @@
         mEventHub(eventHub),
         mPolicy(policy),
         mNextListener(listener),
+        mKeyboardClassifier(std::make_unique<KeyboardClassifier>()),
         mGlobalMetaState(AMETA_NONE),
         mLedMetaState(AMETA_NONE),
         mGeneration(1),
@@ -155,6 +181,9 @@
         }
 
         if (oldGeneration != mGeneration) {
+            // Reset global meta state because it depends on connected input devices.
+            updateGlobalMetaStateLocked();
+
             inputDevicesChanged = true;
             inputDevices = getInputDevicesLocked();
             mPendingArgs.emplace_back(
@@ -162,6 +191,11 @@
         }
 
         std::swap(notifyArgs, mPendingArgs);
+
+        // Keep track of the last used device
+        for (const NotifyArgs& args : notifyArgs) {
+            mLastUsedDeviceId = getDeviceIdOfNewGesture(args).value_or(mLastUsedDeviceId);
+        }
     } // release lock
 
     // Flush queued events out to the listener.
@@ -217,9 +251,6 @@
                 case EventHubInterface::DEVICE_REMOVED:
                     removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                     break;
-                case EventHubInterface::FINISHED_DEVICE_SCAN:
-                    handleConfigurationChangedLocked(rawEvent->when);
-                    break;
                 default:
                     ALOG_ASSERT(false); // can't happen
                     break;
@@ -384,14 +415,6 @@
     return ++mNextInputDeviceId;
 }
 
-void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
-    // Reset global meta state because it depends on the list of all configured devices.
-    updateGlobalMetaStateLocked();
-
-    // Enqueue configuration changed.
-    mPendingArgs.emplace_back(NotifyConfigurationChangedArgs{mContext.getNextId(), when});
-}
-
 void InputReader::refreshConfigurationLocked(ConfigurationChanges changes) {
     mPolicy->getReaderConfiguration(&mConfig);
     mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
@@ -402,10 +425,6 @@
     ALOGI("Reconfiguring input devices, changes=%s", changes.string().c_str());
     nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
 
-    if (changes.test(Change::DISPLAY_INFO)) {
-        updatePointerDisplayLocked();
-    }
-
     if (changes.test(Change::MUST_REOPEN)) {
         mEventHub->requestReopenDevices();
     } else {
@@ -490,47 +509,6 @@
     }
 }
 
-std::shared_ptr<PointerControllerInterface> InputReader::getPointerControllerLocked(
-        int32_t deviceId) {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        controller = mPolicy->obtainPointerController(deviceId);
-        mPointerController = controller;
-        updatePointerDisplayLocked();
-    }
-    return controller;
-}
-
-void InputReader::updatePointerDisplayLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller == nullptr) {
-        return;
-    }
-
-    std::optional<DisplayViewport> viewport =
-            mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
-    if (!viewport) {
-        ALOGW("Can't find the designated viewport with ID %" PRId32 " to update cursor input "
-              "mapper. Fall back to default display",
-              mConfig.defaultPointerDisplayId);
-        viewport = mConfig.getDisplayViewportById(ADISPLAY_ID_DEFAULT);
-    }
-    if (!viewport) {
-        ALOGE("Still can't find a viable viewport to update cursor input mapper. Skip setting it to"
-              " PointerController.");
-        return;
-    }
-
-    controller->setDisplayViewport(*viewport);
-}
-
-void InputReader::fadePointerLocked() {
-    std::shared_ptr<PointerControllerInterface> controller = mPointerController.lock();
-    if (controller != nullptr) {
-        controller->fade(PointerControllerInterface::Transition::GRADUAL);
-    }
-}
-
 void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
     if (when < mNextTimeout) {
         mNextTimeout = when;
@@ -647,15 +625,6 @@
     return result;
 }
 
-void InputReader::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
-    std::scoped_lock _l(mLock);
-
-    InputDevice* device = findInputDeviceLocked(deviceId);
-    if (device != nullptr) {
-        device->addKeyRemapping(fromKeyCode, toKeyCode);
-    }
-}
-
 int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
     std::scoped_lock _l(mLock);
 
@@ -840,6 +809,18 @@
     return device->getDeviceInfo().getSensors();
 }
 
+std::optional<HardwareProperties> InputReader::getTouchpadHardwareProperties(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+
+    if (device == nullptr) {
+        return {};
+    }
+
+    return device->getTouchpadHardwareProperties();
+}
+
 bool InputReader::setLightColor(int32_t deviceId, int32_t lightId, int32_t color) {
     std::scoped_lock _l(mLock);
 
@@ -890,18 +871,7 @@
     return std::nullopt;
 }
 
-bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
-    std::scoped_lock _l(mLock);
-
-    InputDevice* device = findInputDeviceLocked(deviceId);
-    if (device) {
-        return device->isEnabled();
-    }
-    ALOGW("Ignoring invalid device id %" PRId32 ".", deviceId);
-    return false;
-}
-
-bool InputReader::canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
+bool InputReader::canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
     std::scoped_lock _l(mLock);
 
     InputDevice* device = findInputDeviceLocked(deviceId);
@@ -915,10 +885,9 @@
         return false;
     }
 
-    std::optional<int32_t> associatedDisplayId = device->getAssociatedDisplayId();
+    std::optional<ui::LogicalDisplayId> associatedDisplayId = device->getAssociatedDisplayId();
     // No associated display. By default, can dispatch to all displays.
-    if (!associatedDisplayId ||
-            *associatedDisplayId == ADISPLAY_ID_NONE) {
+    if (!associatedDisplayId || !associatedDisplayId->isValid()) {
         return true;
     }
 
@@ -929,6 +898,17 @@
     mEventHub->sysfsNodeChanged(sysfsNodePath);
 }
 
+DeviceId InputReader::getLastUsedInputDeviceId() {
+    std::scoped_lock _l(mLock);
+    return mLastUsedDeviceId;
+}
+
+void InputReader::notifyMouseCursorFadedOnTyping() {
+    std::scoped_lock _l(mLock);
+    // disable touchpad taps when cursor has faded due to typing
+    mPreventingTouchpadTaps = true;
+}
+
 void InputReader::dump(std::string& dump) {
     std::scoped_lock _l(mLock);
 
@@ -1067,17 +1047,6 @@
     return mReader->shouldDropVirtualKeyLocked(now, keyCode, scanCode);
 }
 
-void InputReader::ContextImpl::fadePointer() {
-    // lock is already held by the input loop
-    mReader->fadePointerLocked();
-}
-
-std::shared_ptr<PointerControllerInterface> InputReader::ContextImpl::getPointerController(
-        int32_t deviceId) {
-    // lock is already held by the input loop
-    return mReader->getPointerControllerLocked(deviceId);
-}
-
 void InputReader::ContextImpl::requestTimeoutAtTime(nsecs_t when) {
     // lock is already held by the input loop
     mReader->requestTimeoutAtTimeLocked(when);
@@ -1110,4 +1079,8 @@
     return mIdGenerator.nextId();
 }
 
+KeyboardClassifier& InputReader::ContextImpl::getKeyboardClassifier() {
+    return *mReader->mKeyboardClassifier;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/controller/PeripheralController.cpp b/services/inputflinger/reader/controller/PeripheralController.cpp
index eabf591..49ad8b5 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -418,7 +418,11 @@
         }
         rawInfos.insert_or_assign(rawId, rawInfo.value());
         // Check if this is a group LEDs for player ID
-        std::regex lightPattern("([a-z]+)([0-9]+)");
+        // The name for the light has already been parsed and is the `function`
+        // value; for player ID lights the function is expected to be `player-#`.
+        // However, the Sony driver will use `sony#` instead on SIXAXIS
+        // gamepads.
+        std::regex lightPattern("(player|sony)-?([0-9]+)");
         std::smatch results;
         if (std::regex_match(rawInfo->name, results, lightPattern)) {
             std::string commonName = results[1].str();
@@ -505,9 +509,14 @@
 
     // Check the rest of raw light infos
     for (const auto& [rawId, rawInfo] : rawInfos) {
-        InputDeviceLightType type = keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end()
-                ? InputDeviceLightType::KEYBOARD_BACKLIGHT
-                : InputDeviceLightType::INPUT;
+        InputDeviceLightType type;
+        if (keyboardBacklightIds.find(rawId) != keyboardBacklightIds.end()) {
+            type = InputDeviceLightType::KEYBOARD_BACKLIGHT;
+        } else if (rawInfo.flags.test(InputLightClass::KEYBOARD_MIC_MUTE)) {
+            type = InputDeviceLightType::KEYBOARD_MIC_MUTE;
+        } else {
+            type = InputDeviceLightType::INPUT;
+        }
 
         // If the node is multi-color led, construct a MULTI_COLOR light
         if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) &&
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index a7e0675..edc3037 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -21,6 +21,7 @@
 #include <filesystem>
 #include <functional>
 #include <map>
+#include <optional>
 #include <ostream>
 #include <string>
 #include <unordered_map>
@@ -70,79 +71,78 @@
 
 /* Describes an absolute axis. */
 struct RawAbsoluteAxisInfo {
-    bool valid{false}; // true if the information is valid, false otherwise
-
     int32_t minValue{};   // minimum value
     int32_t maxValue{};   // maximum value
     int32_t flat{};       // center flat position, eg. flat == 8 means center is between -8 and 8
     int32_t fuzz{};       // error tolerance, eg. fuzz == 4 means value is +/- 4 due to noise
     int32_t resolution{}; // resolution in units per mm or radians per mm
-
-    inline void clear() { *this = RawAbsoluteAxisInfo(); }
 };
 
-std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info);
+std::ostream& operator<<(std::ostream& out, const std::optional<RawAbsoluteAxisInfo>& info);
 
 /*
  * Input device classes.
+ *
+ * These classes are duplicated in rust side here: /frameworks/native/libs/input/rust/input.rs.
+ * If any new classes are added, we need to add them in rust input side too.
  */
 enum class InputDeviceClass : uint32_t {
     /* The input device is a keyboard or has buttons. */
-    KEYBOARD = 0x00000001,
+    KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD,
 
     /* The input device is an alpha-numeric keyboard (not just a dial pad). */
-    ALPHAKEY = 0x00000002,
+    ALPHAKEY = android::os::IInputConstants::DEVICE_CLASS_ALPHAKEY,
 
     /* The input device is a touchscreen or a touchpad (either single-touch or multi-touch). */
-    TOUCH = 0x00000004,
+    TOUCH = android::os::IInputConstants::DEVICE_CLASS_TOUCH,
 
     /* The input device is a cursor device such as a trackball or mouse. */
-    CURSOR = 0x00000008,
+    CURSOR = android::os::IInputConstants::DEVICE_CLASS_CURSOR,
 
     /* The input device is a multi-touch touchscreen or touchpad. */
-    TOUCH_MT = 0x00000010,
+    TOUCH_MT = android::os::IInputConstants::DEVICE_CLASS_TOUCH_MT,
 
     /* The input device is a directional pad (implies keyboard, has DPAD keys). */
-    DPAD = 0x00000020,
+    DPAD = android::os::IInputConstants::DEVICE_CLASS_DPAD,
 
     /* The input device is a gamepad (implies keyboard, has BUTTON keys). */
-    GAMEPAD = 0x00000040,
+    GAMEPAD = android::os::IInputConstants::DEVICE_CLASS_GAMEPAD,
 
     /* The input device has switches. */
-    SWITCH = 0x00000080,
+    SWITCH = android::os::IInputConstants::DEVICE_CLASS_SWITCH,
 
     /* The input device is a joystick (implies gamepad, has joystick absolute axes). */
-    JOYSTICK = 0x00000100,
+    JOYSTICK = android::os::IInputConstants::DEVICE_CLASS_JOYSTICK,
 
     /* The input device has a vibrator (supports FF_RUMBLE). */
-    VIBRATOR = 0x00000200,
+    VIBRATOR = android::os::IInputConstants::DEVICE_CLASS_VIBRATOR,
 
     /* The input device has a microphone. */
-    MIC = 0x00000400,
+    MIC = android::os::IInputConstants::DEVICE_CLASS_MIC,
 
     /* The input device is an external stylus (has data we want to fuse with touch data). */
-    EXTERNAL_STYLUS = 0x00000800,
+    EXTERNAL_STYLUS = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL_STYLUS,
 
     /* The input device has a rotary encoder */
-    ROTARY_ENCODER = 0x00001000,
+    ROTARY_ENCODER = android::os::IInputConstants::DEVICE_CLASS_ROTARY_ENCODER,
 
     /* The input device has a sensor like accelerometer, gyro, etc */
-    SENSOR = 0x00002000,
+    SENSOR = android::os::IInputConstants::DEVICE_CLASS_SENSOR,
 
     /* The input device has a battery */
-    BATTERY = 0x00004000,
+    BATTERY = android::os::IInputConstants::DEVICE_CLASS_BATTERY,
 
     /* The input device has sysfs controllable lights */
-    LIGHT = 0x00008000,
+    LIGHT = android::os::IInputConstants::DEVICE_CLASS_LIGHT,
 
     /* The input device is a touchpad, requiring an on-screen cursor. */
-    TOUCHPAD = 0x00010000,
+    TOUCHPAD = android::os::IInputConstants::DEVICE_CLASS_TOUCHPAD,
 
     /* The input device is virtual (not a real device, not part of UI configuration). */
-    VIRTUAL = 0x40000000,
+    VIRTUAL = android::os::IInputConstants::DEVICE_CLASS_VIRTUAL,
 
     /* The input device is external (not built-in). */
-    EXTERNAL = 0x80000000,
+    EXTERNAL = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL,
 };
 
 enum class SysfsClass : uint32_t {
@@ -177,6 +177,8 @@
     MAX_BRIGHTNESS = 0x00000080,
     /* The input light has kbd_backlight name */
     KEYBOARD_BACKLIGHT = 0x00000100,
+    /* The input light has mic_mute name */
+    KEYBOARD_MIC_MUTE = 0x00000200,
 };
 
 enum class InputBatteryClass : uint32_t {
@@ -252,9 +254,6 @@
         DEVICE_ADDED = 0x10000000,
         // Sent when a device is removed.
         DEVICE_REMOVED = 0x20000000,
-        // Sent when all added/removed devices from the most recent scan have been reported.
-        // This event is always sent at least once.
-        FINISHED_DEVICE_SCAN = 0x30000000,
 
         FIRST_SYNTHETIC_EVENT = DEVICE_ADDED,
     };
@@ -273,8 +272,8 @@
      */
     virtual std::optional<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
 
-    virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                         RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+    virtual std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                                   int axis) const = 0;
 
     virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
 
@@ -282,8 +281,8 @@
 
     virtual bool hasMscEvent(int32_t deviceId, int mscEvent) const = 0;
 
-    virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                                 int32_t toKeyCode) const = 0;
+    virtual void setKeyRemapping(int32_t deviceId,
+                                 const std::map<int32_t, int32_t>& keyRemapping) const = 0;
 
     virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
                             int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
@@ -334,8 +333,7 @@
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
     virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
     virtual int32_t getSwitchState(int32_t deviceId, int32_t sw) const = 0;
-    virtual status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                          int32_t* outValue) const = 0;
+    virtual std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const = 0;
     /* Query Multi-Touch slot values for an axis. Returns error or an 1 indexed array of size
      * (slotCount + 1). The value at the 0 index is set to queried axis. */
     virtual base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
@@ -506,8 +504,8 @@
 
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
 
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override final;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override final;
 
     bool hasRelativeAxis(int32_t deviceId, int axis) const override final;
 
@@ -515,8 +513,8 @@
 
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                         int32_t toKeyCode) const override final;
+    void setKeyRemapping(int32_t deviceId,
+                         const std::map<int32_t, int32_t>& keyRemapping) const override final;
 
     status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
                     int32_t* outKeycode, int32_t* outMetaState,
@@ -554,8 +552,8 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override final;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId,
                                      int32_t locationKeyCode) const override final;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override final;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId,
+                                                int32_t axis) const override final;
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override final;
 
@@ -788,7 +786,6 @@
     std::vector<std::unique_ptr<Device>> mOpeningDevices;
     std::vector<std::unique_ptr<Device>> mClosingDevices;
 
-    bool mNeedToSendFinishedDeviceScan;
     bool mNeedToReopenDevices;
     bool mNeedToScanDevices;
     std::vector<std::string> mExcludedDevices;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 0719b0c..62cc4da 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -43,7 +43,7 @@
 public:
     InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
                 const InputDeviceIdentifier& identifier);
-    ~InputDevice();
+    virtual ~InputDevice();
 
     inline InputReaderContext* getContext() { return mContext; }
     inline int32_t getId() const { return mId; }
@@ -56,26 +56,31 @@
     }
     inline const std::string getLocation() const { return mIdentifier.location; }
     inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
-    inline uint32_t getSources() const { return mSources; }
+    inline virtual uint32_t getSources() const { return mSources; }
     inline bool hasEventHubDevices() const { return !mDevices.empty(); }
 
     inline bool isExternal() { return mIsExternal; }
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mAssociatedDisplayPort;
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mAssociatedDisplayUniqueId;
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mAssociatedDisplayUniqueIdByPort;
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mAssociatedDisplayUniqueIdByDescriptor;
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mAssociatedDeviceType;
     }
-    inline std::optional<DisplayViewport> getAssociatedViewport() const {
+    inline virtual std::optional<DisplayViewport> getAssociatedViewport() const {
         return mAssociatedViewport;
     }
     inline bool hasMic() const { return mHasMic; }
 
     inline bool isIgnored() { return !getMapperCount() && !mController; }
 
+    inline KeyboardType getKeyboardType() const { return mKeyboardType; }
+
     bool isEnabled();
 
     void dump(std::string& dump, const std::string& eventHubDevStr);
@@ -119,21 +124,23 @@
     int32_t getMetaState();
     void updateMetaState(int32_t keyCode);
 
-    void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
+    void setKeyboardType(KeyboardType keyboardType);
 
     void bumpGeneration();
 
     [[nodiscard]] NotifyDeviceResetArgs notifyReset(nsecs_t when);
 
-    inline const PropertyMap& getConfiguration() { return mConfiguration; }
+    inline virtual const PropertyMap& getConfiguration() const { return mConfiguration; }
     inline EventHubInterface* getEventHub() { return mContext->getEventHub(); }
 
-    std::optional<int32_t> getAssociatedDisplayId();
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId();
 
     void updateLedState(bool reset);
 
     size_t getMapperCount();
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties();
+
     // construct and add a mapper to the input device
     template <class T, typename... Args>
     T& addMapper(int32_t eventHubId, Args... args) {
@@ -193,8 +200,10 @@
     uint32_t mSources;
     bool mIsWaking;
     bool mIsExternal;
+    KeyboardType mKeyboardType = KeyboardType::NONE;
     std::optional<uint8_t> mAssociatedDisplayPort;
-    std::optional<std::string> mAssociatedDisplayUniqueId;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByPort;
+    std::optional<std::string> mAssociatedDisplayUniqueIdByDescriptor;
     std::optional<std::string> mAssociatedDeviceType;
     std::optional<DisplayViewport> mAssociatedViewport;
     bool mHasMic;
@@ -290,25 +299,24 @@
     inline ftl::Flags<InputDeviceClass> getDeviceClasses() const {
         return mEventHub->getDeviceClasses(mId);
     }
+    inline uint32_t getDeviceSources() const { return mDevice.getSources(); }
     inline InputDeviceIdentifier getDeviceIdentifier() const {
         return mEventHub->getDeviceIdentifier(mId);
     }
     inline int32_t getDeviceControllerNumber() const {
         return mEventHub->getDeviceControllerNumber(mId);
     }
-    inline status_t getAbsoluteAxisInfo(int32_t code, RawAbsoluteAxisInfo* axisInfo) const {
-        if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
-            return status;
-        }
+    inline std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t code) const {
+        std::optional<RawAbsoluteAxisInfo> info = mEventHub->getAbsoluteAxisInfo(mId, code);
 
         // Validate axis info for InputDevice.
-        if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
+        if (info && info->minValue == info->maxValue) {
             // Historically, we deem axes with the same min and max values as invalid to avoid
             // dividing by zero when scaling by max - min.
             // TODO(b/291772515): Perform axis info validation on a per-axis basis when it is used.
-            axisInfo->valid = false;
+            return std::nullopt;
         }
-        return OK;
+        return info;
     }
     inline bool hasRelativeAxis(int32_t code) const {
         return mEventHub->hasRelativeAxis(mId, code);
@@ -319,8 +327,8 @@
 
     inline bool hasMscEvent(int mscEvent) const { return mEventHub->hasMscEvent(mId, mscEvent); }
 
-    inline void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) const {
-        mEventHub->addKeyRemapping(mId, fromKeyCode, toKeyCode);
+    inline void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) const {
+        mEventHub->setKeyRemapping(mId, keyRemapping);
     }
 
     inline status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t metaState,
@@ -370,8 +378,8 @@
         return mEventHub->getKeyCodeForKeyLocation(mId, locationKeyCode);
     }
     inline int32_t getSwitchState(int32_t sw) const { return mEventHub->getSwitchState(mId, sw); }
-    inline status_t getAbsoluteAxisValue(int32_t code, int32_t* outValue) const {
-        return mEventHub->getAbsoluteAxisValue(mId, code, outValue);
+    inline std::optional<int32_t> getAbsoluteAxisValue(int32_t code) const {
+        return mEventHub->getAbsoluteAxisValue(mId, code);
     }
     inline base::Result<std::vector<int32_t>> getMtSlotValues(int32_t axis,
                                                               size_t slotCount) const {
@@ -423,9 +431,7 @@
     }
 
     inline bool hasAbsoluteAxis(int32_t code) const {
-        RawAbsoluteAxisInfo info;
-        mEventHub->getAbsoluteAxisInfo(mId, code, &info);
-        return info.valid;
+        return mEventHub->getAbsoluteAxisInfo(mId, code).has_value();
     }
     inline bool isKeyPressed(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode) == AKEY_STATE_DOWN;
@@ -433,11 +439,6 @@
     inline bool isKeyCodePressed(int32_t keyCode) const {
         return mEventHub->getKeyCodeState(mId, keyCode) == AKEY_STATE_DOWN;
     }
-    inline int32_t getAbsoluteAxisValue(int32_t code) const {
-        int32_t value;
-        mEventHub->getAbsoluteAxisValue(mId, code, &value);
-        return value;
-    }
     inline bool isDeviceEnabled() { return mEventHub->isDeviceEnabled(mId); }
     inline status_t enableDevice() { return mEventHub->enableDevice(mId); }
     inline status_t disableDevice() { return mEventHub->disableDevice(mId); }
@@ -449,8 +450,11 @@
     inline std::optional<uint8_t> getAssociatedDisplayPort() const {
         return mDevice.getAssociatedDisplayPort();
     }
-    inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
-        return mDevice.getAssociatedDisplayUniqueId();
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByPort() const {
+        return mDevice.getAssociatedDisplayUniqueIdByPort();
+    }
+    inline std::optional<std::string> getAssociatedDisplayUniqueIdByDescriptor() const {
+        return mDevice.getAssociatedDisplayUniqueIdByDescriptor();
     }
     inline std::optional<std::string> getDeviceTypeAssociation() const {
         return mDevice.getDeviceTypeAssociation();
@@ -463,6 +467,10 @@
     }
     inline void bumpGeneration() { mDevice.bumpGeneration(); }
     inline const PropertyMap& getConfiguration() const { return mDevice.getConfiguration(); }
+    inline KeyboardType getKeyboardType() const { return mDevice.getKeyboardType(); }
+    inline void setKeyboardType(KeyboardType keyboardType) {
+        return mDevice.setKeyboardType(keyboardType);
+    }
 
 private:
     InputDevice& mDevice;
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 4c78db3..1003871 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <PointerControllerInterface.h>
 #include <android-base/thread_annotations.h>
 #include <utils/Condition.h>
 #include <utils/Mutex.h>
@@ -62,14 +61,10 @@
 
     std::vector<InputDeviceInfo> getInputDevices() const override;
 
-    bool isInputDeviceEnabled(int32_t deviceId) override;
-
     int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask, int32_t scanCode) override;
     int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override;
     int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override;
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override;
-
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     void toggleCapsLockState(int32_t deviceId) override;
@@ -87,7 +82,7 @@
 
     std::vector<int32_t> getVibratorIds(int32_t deviceId) override;
 
-    bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) override;
+    bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) override;
 
     bool enableSensor(int32_t deviceId, InputDeviceSensorType sensorType,
                       std::chrono::microseconds samplingPeriod,
@@ -107,6 +102,8 @@
 
     std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) override;
 
+    std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) override;
+
     bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) override;
 
     bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) override;
@@ -119,6 +116,10 @@
 
     void sysfsNodeChanged(const std::string& sysfsNodePath) override;
 
+    DeviceId getLastUsedInputDeviceId() override;
+
+    void notifyMouseCursorFadedOnTyping() override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
@@ -141,9 +142,6 @@
         void disableVirtualKeysUntil(nsecs_t time) REQUIRES(mReader->mLock) override;
         bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode)
                 REQUIRES(mReader->mLock) override;
-        void fadePointer() REQUIRES(mReader->mLock) override;
-        std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId)
-                REQUIRES(mReader->mLock) override;
         void requestTimeoutAtTime(nsecs_t when) REQUIRES(mReader->mLock) override;
         int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
         void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
@@ -161,6 +159,7 @@
         void setLastKeyDownTimestamp(nsecs_t when) REQUIRES(mReader->mLock)
                 REQUIRES(mLock) override;
         nsecs_t getLastKeyDownTimestamp() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
+        KeyboardClassifier& getKeyboardClassifier() override;
     } mContext;
 
     friend class ContextImpl;
@@ -180,6 +179,10 @@
 
     // The next stage that should receive the events generated inside InputReader.
     InputListenerInterface& mNextListener;
+
+    // Classifier for keyboard/keyboard-like devices
+    std::unique_ptr<KeyboardClassifier> mKeyboardClassifier;
+
     // As various events are generated inside InputReader, they are stored inside this list. The
     // list can only be accessed with the lock, so the events inside it are well-ordered.
     // Once the reader is done working, these events will be swapped into a temporary storage and
@@ -198,12 +201,15 @@
     std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
             mDeviceToEventHubIdsMap GUARDED_BY(mLock);
 
-    // true if tap-to-click on touchpad currently disabled
+    // true if tap-to-click on touchpad is currently disabled
     bool mPreventingTouchpadTaps GUARDED_BY(mLock){false};
 
     // records timestamp of the last key press on the physical keyboard
     nsecs_t mLastKeyDownTimestamp GUARDED_BY(mLock){0};
 
+    // The input device that produced a new gesture most recently.
+    DeviceId mLastUsedDeviceId GUARDED_BY(mLock){ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID};
+
     // low-level input event decoding and device management
     [[nodiscard]] std::list<NotifyArgs> processEventsLocked(const RawEvent* rawEvents, size_t count)
             REQUIRES(mLock);
@@ -215,8 +221,6 @@
                                                                      size_t count) REQUIRES(mLock);
     [[nodiscard]] std::list<NotifyArgs> timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock);
 
-    void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock);
-
     int32_t mGlobalMetaState GUARDED_BY(mLock);
     void updateGlobalMetaStateLocked() REQUIRES(mLock);
     int32_t getGlobalMetaStateLocked() REQUIRES(mLock);
@@ -230,13 +234,6 @@
     [[nodiscard]] std::list<NotifyArgs> dispatchExternalStylusStateLocked(const StylusState& state)
             REQUIRES(mLock);
 
-    // The PointerController that is shared among all the input devices that need it.
-    std::weak_ptr<PointerControllerInterface> mPointerController;
-    std::shared_ptr<PointerControllerInterface> getPointerControllerLocked(int32_t deviceId)
-            REQUIRES(mLock);
-    void updatePointerDisplayLocked() REQUIRES(mLock);
-    void fadePointerLocked() REQUIRES(mLock);
-
     int32_t mGeneration GUARDED_BY(mLock);
     int32_t bumpGenerationLocked() REQUIRES(mLock);
 
diff --git a/services/inputflinger/reader/include/InputReaderContext.h b/services/inputflinger/reader/include/InputReaderContext.h
index 69b2315..e0e0ac2 100644
--- a/services/inputflinger/reader/include/InputReaderContext.h
+++ b/services/inputflinger/reader/include/InputReaderContext.h
@@ -17,6 +17,7 @@
 #pragma once
 
 #include <input/InputDevice.h>
+#include <input/KeyboardClassifier.h>
 #include "NotifyArgs.h"
 
 #include <vector>
@@ -28,7 +29,6 @@
 class InputListenerInterface;
 class InputMapper;
 class InputReaderPolicyInterface;
-class PointerControllerInterface;
 struct StylusState;
 
 /* Internal interface used by individual input devices to access global input device state
@@ -45,9 +45,6 @@
     virtual void disableVirtualKeysUntil(nsecs_t time) = 0;
     virtual bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) = 0;
 
-    virtual void fadePointer() = 0;
-    virtual std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) = 0;
-
     virtual void requestTimeoutAtTime(nsecs_t when) = 0;
     virtual int32_t bumpGeneration() = 0;
 
@@ -68,6 +65,8 @@
 
     virtual void setLastKeyDownTimestamp(nsecs_t when) = 0;
     virtual nsecs_t getLastKeyDownTimestamp() = 0;
+
+    virtual KeyboardClassifier& getKeyboardClassifier() = 0;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index 061c6a3..dd46bbc 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -16,18 +16,23 @@
 
 #include "CapturedTouchpadEventConverter.h"
 
+#include <optional>
 #include <sstream>
 
 #include <android-base/stringprintf.h>
-#include <gui/constants.h>
+#include <com_android_input_flags.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
 
+static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+
 int32_t actionWithIndex(int32_t action, int32_t index) {
     return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 }
@@ -43,6 +48,12 @@
     return i;
 }
 
+void addRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                       RawAbsoluteAxisInfo& evdevAxis) {
+    deviceInfo.addMotionRange(androidAxis, SOURCE, evdevAxis.minValue, evdevAxis.maxValue,
+                              evdevAxis.flat, evdevAxis.fuzz, evdevAxis.resolution);
+}
+
 } // namespace
 
 CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
@@ -54,32 +65,33 @@
         mMotionAccumulator(motionAccumulator),
         mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
         mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
-    RawAbsoluteAxisInfo orientationInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
-    if (orientationInfo.valid) {
-        if (orientationInfo.maxValue > 0) {
-            mOrientationScale = M_PI_2 / orientationInfo.maxValue;
-        } else if (orientationInfo.minValue < 0) {
-            mOrientationScale = -M_PI_2 / orientationInfo.minValue;
+    if (std::optional<RawAbsoluteAxisInfo> orientation =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        orientation) {
+        if (orientation->maxValue > 0) {
+            mOrientationScale = M_PI_2 / orientation->maxValue;
+        } else if (orientation->minValue < 0) {
+            mOrientationScale = -M_PI_2 / orientation->minValue;
         }
     }
 
     // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
-    RawAbsoluteAxisInfo pressureInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
-    if (pressureInfo.valid && pressureInfo.maxValue > 0) {
-        mPressureScale = 1.0 / pressureInfo.maxValue;
+    if (std::optional<RawAbsoluteAxisInfo> pressure =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE);
+        pressure && pressure->maxValue > 0) {
+        mPressureScale = 1.0 / pressure->maxValue;
     }
 
-    RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo);
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo);
-    mHasTouchMajor = touchMajorInfo.valid;
-    mHasToolMajor = toolMajorInfo.valid;
-    if (mHasTouchMajor && touchMajorInfo.maxValue != 0) {
-        mSizeScale = 1.0f / touchMajorInfo.maxValue;
-    } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) {
-        mSizeScale = 1.0f / toolMajorInfo.maxValue;
+    std::optional<RawAbsoluteAxisInfo> touchMajor =
+            deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
+    std::optional<RawAbsoluteAxisInfo> toolMajor =
+            deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
+    mHasTouchMajor = touchMajor.has_value();
+    mHasToolMajor = toolMajor.has_value();
+    if (mHasTouchMajor && touchMajor->maxValue != 0) {
+        mSizeScale = 1.0f / touchMajor->maxValue;
+    } else if (mHasToolMajor && toolMajor->maxValue != 0) {
+        mSizeScale = 1.0f / toolMajor->maxValue;
     }
 }
 
@@ -107,22 +119,27 @@
 }
 
 void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_X,
+                                         AMOTION_EVENT_AXIS_RELATIVE_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_Y,
+                                         AMOTION_EVENT_AXIS_RELATIVE_Y, ABS_MT_POSITION_Y);
+    } else {
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    }
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
 
-    RawAbsoluteAxisInfo pressureInfo;
-    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
-    if (pressureInfo.valid) {
+    if (mDeviceContext.hasAbsoluteAxis(ABS_MT_PRESSURE)) {
         info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
     }
 
-    RawAbsoluteAxisInfo orientationInfo;
-    mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
-    if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) {
+    if (std::optional<RawAbsoluteAxisInfo> orientation =
+                mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        orientation && (orientation->maxValue > 0 || orientation->minValue < 0)) {
         info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
     }
 
@@ -134,11 +151,25 @@
 void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
                                                           int32_t androidAxis,
                                                           int32_t evdevAxis) const {
-    RawAbsoluteAxisInfo info;
-    mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info);
-    if (info.valid) {
-        deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat,
-                                  info.fuzz, info.resolution);
+    std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
+    if (info) {
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *info);
+    }
+}
+
+void CapturedTouchpadEventConverter::tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo,
+                                                                      int32_t androidAxis,
+                                                                      int32_t androidRelativeAxis,
+                                                                      int32_t evdevAxis) const {
+    std::optional<RawAbsoluteAxisInfo> axisInfo = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
+    if (axisInfo) {
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *axisInfo);
+
+        // The largest movement we could possibly report on a relative axis is from the minimum to
+        // the maximum (or vice versa) of the absolute axis.
+        float range = axisInfo->maxValue - axisInfo->minValue;
+        deviceInfo.addMotionRange(androidRelativeAxis, SOURCE, -range, range, axisInfo->flat,
+                                  axisInfo->fuzz, axisInfo->resolution);
     }
 }
 
@@ -156,8 +187,8 @@
         mMotionAccumulator.finishSync();
     }
 
-    mCursorButtonAccumulator.process(&rawEvent);
-    mMotionAccumulator.process(&rawEvent);
+    mCursorButtonAccumulator.process(rawEvent);
+    mMotionAccumulator.process(rawEvent);
     return out;
 }
 
@@ -165,7 +196,7 @@
     std::list<NotifyArgs> out;
     std::vector<PointerCoords> coords;
     std::vector<PointerProperties> properties;
-    std::map<size_t, size_t> coordsIndexForSlotNumber;
+    std::map<size_t /*slotNumber*/, size_t /*coordsIndex*/> coordsIndexForSlotNumber;
 
     // For all the touches that were already down, send a MOVE event with their updated coordinates.
     // A convention of the MotionEvent API is that pointer coordinates in UP events match the
@@ -177,11 +208,19 @@
             // to stay perfectly still between frames, and if it does the worst that can happen is
             // an extra MOVE event, so it's not worth the overhead of checking for changes.
             coordsIndexForSlotNumber[slotNumber] = coords.size();
-            coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+            coords.push_back(makePointerCoordsForSlot(slotNumber));
             properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
         }
         out.push_back(
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            // For any further events we send from this sync, the pointers won't have moved relative
+            // to the positions we just reported in this MOVE event, so zero out the relative axes.
+            for (PointerCoords& pointer : coords) {
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+            }
+        }
     }
 
     std::vector<size_t> upSlots, downSlots;
@@ -236,6 +275,9 @@
                                      /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
 
         freePointerIdForSlot(slotNumber);
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            mPreviousCoordsForSlotNumber.erase(slotNumber);
+        }
         coords.erase(coords.begin() + indexToRemove);
         properties.erase(properties.begin() + indexToRemove);
         // Now that we've removed some coords and properties, we might have to update the slot
@@ -256,7 +298,7 @@
                 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
 
         coordsIndexForSlotNumber[slotNumber] = coordsIndex;
-        coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+        coords.push_back(makePointerCoordsForSlot(slotNumber));
         properties.push_back(
                 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
 
@@ -279,7 +321,7 @@
     LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
                         "Mismatched coords and properties arrays.");
     return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
-                            ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
+                            ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action,
                             /*actionButton=*/actionButton, flags,
                             mReaderContext.getGlobalMetaState(), mButtonState,
                             MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
@@ -288,12 +330,22 @@
                             AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
 }
 
-PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
-        const MultiTouchMotionAccumulator::Slot& slot) const {
+PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(size_t slotNumber) {
+    const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(slotNumber);
     PointerCoords coords;
     coords.clear();
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        if (auto it = mPreviousCoordsForSlotNumber.find(slotNumber);
+            it != mPreviousCoordsForSlotNumber.end()) {
+            auto [oldX, oldY] = it->second;
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, slot.getX() - oldX);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, slot.getY() - oldY);
+        }
+        mPreviousCoordsForSlotNumber[slotNumber] = std::make_pair(slot.getX(), slot.getY());
+    }
+
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
index 9b6df7a..d6c0708 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
@@ -21,6 +21,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android/input.h>
@@ -49,12 +50,14 @@
 private:
     void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
                               int32_t evdevAxis) const;
+    void tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                                          int32_t androidRelativeAxis, int32_t evdevAxis) const;
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   const std::vector<PointerCoords>& coords,
                                                   const std::vector<PointerProperties>& properties,
                                                   int32_t actionButton = 0, int32_t flags = 0);
-    PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const;
+    PointerCoords makePointerCoordsForSlot(size_t slotNumber);
     int32_t allocatePointerIdToSlot(size_t slotNumber);
     void freePointerIdForSlot(size_t slotNumber);
 
@@ -76,8 +79,7 @@
 
     std::bitset<MAX_POINTER_ID + 1> mPointerIdsInUse;
     std::map<size_t, int32_t> mPointerIdForSlotNumber;
-
-    static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+    std::map<size_t, std::pair<float, float>> mPreviousCoordsForSlotNumber;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 06f10e5..20cdb59 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -40,8 +40,6 @@
 // The default velocity control parameters that has no effect.
 static const VelocityControlParameters FLAT_VELOCITY_CONTROL_PARAMS{};
 
-static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
-
 // --- CursorMotionAccumulator ---
 
 CursorMotionAccumulator::CursorMotionAccumulator() {
@@ -57,14 +55,14 @@
     mRelY = 0;
 }
 
-void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_REL) {
-        switch (rawEvent->code) {
+void CursorMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_REL) {
+        switch (rawEvent.code) {
             case REL_X:
-                mRelX = rawEvent->value;
+                mRelX = rawEvent.value;
                 break;
             case REL_Y:
-                mRelY = rawEvent->value;
+                mRelY = rawEvent.value;
                 break;
         }
     }
@@ -78,22 +76,10 @@
 
 CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
                                      const InputReaderConfiguration& readerConfig)
-      : CursorInputMapper(deviceContext, readerConfig, ENABLE_POINTER_CHOREOGRAPHER) {}
-
-CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
-                                     const InputReaderConfiguration& readerConfig,
-                                     bool enablePointerChoreographer)
       : InputMapper(deviceContext, readerConfig),
         mLastEventTime(std::numeric_limits<nsecs_t>::min()),
-        mEnablePointerChoreographer(enablePointerChoreographer),
         mEnableNewMousePointerBallistics(input_flags::enable_new_mouse_pointer_ballistics()) {}
 
-CursorInputMapper::~CursorInputMapper() {
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-    }
-}
-
 uint32_t CursorInputMapper::getSources() const {
     return mSource;
 }
@@ -143,7 +129,8 @@
                          mWheelXVelocityControl.getParameters().dump().c_str());
     dump += StringPrintf(INDENT3 "VWheelScale: %0.3f\n", mVWheelScale);
     dump += StringPrintf(INDENT3 "HWheelScale: %0.3f\n", mHWheelScale);
-    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n",
+                         toString(mDisplayId, streamableToString).c_str());
     dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(mOrientation).c_str());
     dump += StringPrintf(INDENT3 "ButtonState: 0x%08x\n", mButtonState);
     dump += StringPrintf(INDENT3 "Down: %s\n", toString(isPointerDown(mButtonState)));
@@ -228,16 +215,16 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> CursorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> CursorInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mCursorButtonAccumulator.process(rawEvent);
     mCursorMotionAccumulator.process(rawEvent);
     mCursorScrollAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
         const auto [eventTime, readTime] =
                 applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(),
-                                                   rawEvent->when, rawEvent->readTime,
+                                                   rawEvent.when, rawEvent.readTime,
                                                    mLastEventTime);
         out += sync(eventTime, readTime);
         mLastEventTime = eventTime;
@@ -304,22 +291,6 @@
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mSource == AINPUT_SOURCE_MOUSE) {
-        if (!mEnablePointerChoreographer) {
-            if (moved || scrolled || buttonsChanged) {
-                mPointerController->setPresentation(
-                        PointerControllerInterface::Presentation::POINTER);
-
-                if (moved) {
-                    mPointerController->move(deltaX, deltaY);
-                }
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            }
-
-            std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
-
-            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-            pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-        }
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
         pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
     } else {
@@ -447,7 +418,7 @@
     }
 }
 
-std::optional<int32_t> CursorInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> CursorInputMapper::getAssociatedDisplayId() {
     return mDisplayId;
 }
 
@@ -470,7 +441,6 @@
             mYPrecision = 1.0f;
             mXScale = 1.0f;
             mYScale = 1.0f;
-            mPointerController = getContext()->getPointerController(getDeviceId());
             break;
         case Parameters::Mode::NAVIGATION:
             mSource = AINPUT_SOURCE_TRACKBALL;
@@ -486,12 +456,10 @@
 }
 
 void CursorInputMapper::configureOnPointerCapture(const InputReaderConfiguration& config) {
-    if (config.pointerCaptureRequest.enable) {
+    if (config.pointerCaptureRequest.isEnable()) {
         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");
         }
@@ -520,13 +488,13 @@
         if (mEnableNewMousePointerBallistics) {
             mNewPointerVelocityControl.setAccelerationEnabled(
                     config.displaysWithMousePointerAccelerationDisabled.count(
-                            mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0);
+                            mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0);
             mNewPointerVelocityControl.setCurve(
                     createAccelerationCurveForPointerSensitivity(config.mousePointerSpeed));
         } else {
             mOldPointerVelocityControl.setParameters(
                     (config.displaysWithMousePointerAccelerationDisabled.count(
-                             mDisplayId.value_or(ADISPLAY_ID_NONE)) == 0)
+                             mDisplayId.value_or(ui::LogicalDisplayId::INVALID)) == 0)
                             ? config.pointerVelocityControlParameters
                             : FLAT_VELOCITY_CONTROL_PARAMS);
         }
@@ -538,40 +506,20 @@
 void CursorInputMapper::configureOnChangeDisplayInfo(const InputReaderConfiguration& config) {
     const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
 
-    mDisplayId = ADISPLAY_ID_NONE;
+    mDisplayId = ui::LogicalDisplayId::INVALID;
     std::optional<DisplayViewport> resolvedViewport;
-    bool isBoundsSet = false;
     if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
         // This InputDevice is associated with a viewport.
         // Only generate events for the associated display.
         mDisplayId = assocViewport->displayId;
         resolvedViewport = *assocViewport;
-        if (!mEnablePointerChoreographer) {
-            const bool mismatchedPointerDisplay =
-                    isPointer && (assocViewport->displayId != mPointerController->getDisplayId());
-            if (mismatchedPointerDisplay) {
-                // This device's associated display doesn't match PointerController's current
-                // display. Do not associate it with any display.
-                mDisplayId.reset();
-            }
-        }
     } else if (isPointer) {
         // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-        if (mEnablePointerChoreographer) {
-            // Always use DISPLAY_ID_NONE for mouse events.
-            // PointerChoreographer will make it target the correct the displayId later.
-            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
-            mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
-        } else {
-            mDisplayId = mPointerController->getDisplayId();
-            if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
-                resolvedViewport = *v;
-            }
-            if (auto bounds = mPointerController->getBounds(); bounds) {
-                mBoundsInLogicalDisplay = *bounds;
-                isBoundsSet = true;
-            }
-        }
+        // Always use DISPLAY_ID_NONE for mouse events.
+        // PointerChoreographer will make it target the correct the displayId later.
+        resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+        mDisplayId =
+                resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID) : std::nullopt;
     }
 
     mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) ||
@@ -579,14 +527,12 @@
             ? ui::ROTATION_0
             : getInverseRotation(resolvedViewport->orientation);
 
-    if (!isBoundsSet) {
-        mBoundsInLogicalDisplay = resolvedViewport
-                ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
-                            static_cast<float>(resolvedViewport->logicalTop),
-                            static_cast<float>(resolvedViewport->logicalRight - 1),
-                            static_cast<float>(resolvedViewport->logicalBottom - 1)}
-                : FloatRect{0, 0, 0, 0};
-    }
+    mBoundsInLogicalDisplay = resolvedViewport
+            ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+                        static_cast<float>(resolvedViewport->logicalTop),
+                        static_cast<float>(resolvedViewport->logicalRight - 1),
+                        static_cast<float>(resolvedViewport->logicalBottom - 1)}
+            : FloatRect{0, 0, 0, 0};
 
     bumpGeneration();
 }
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index ca541d9..3fc370c 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -20,24 +20,18 @@
 #include "CursorScrollAccumulator.h"
 #include "InputMapper.h"
 
-#include <PointerControllerInterface.h>
 #include <input/VelocityControl.h>
 #include <ui/Rotation.h>
 
 namespace android {
 
-class PointerControllerInterface;
-
-class CursorButtonAccumulator;
-class CursorScrollAccumulator;
-
 /* Keeps track of cursor movements. */
 class CursorMotionAccumulator {
 public:
     CursorMotionAccumulator();
     void reset(InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     inline int32_t getRelativeX() const { return mRelX; }
@@ -56,7 +50,7 @@
     friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
                                                 const InputReaderConfiguration& readerConfig,
                                                 Args... args);
-    virtual ~CursorInputMapper();
+    virtual ~CursorInputMapper() = default;
 
     virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
@@ -65,11 +59,11 @@
                                                     const InputReaderConfiguration& readerConfig,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
 
-    virtual std::optional<int32_t> getAssociatedDisplayId() override;
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
 private:
     // Amount that trackball needs to move in order to generate a key event.
@@ -116,27 +110,20 @@
     SimpleVelocityControl mWheelYVelocityControl;
 
     // The display that events generated by this mapper should target. This can be set to
-    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     ui::Rotation mOrientation{ui::ROTATION_0};
     FloatRect mBoundsInLogicalDisplay{};
 
-    std::shared_ptr<PointerControllerInterface> mPointerController;
-
     int32_t mButtonState;
     nsecs_t mDownTime;
     nsecs_t mLastEventTime;
 
-    const bool mEnablePointerChoreographer;
     const bool mEnableNewMousePointerBallistics;
 
     explicit CursorInputMapper(InputDeviceContext& deviceContext,
                                const InputReaderConfiguration& readerConfig);
-    // Constructor for testing.
-    explicit CursorInputMapper(InputDeviceContext& deviceContext,
-                               const InputReaderConfiguration& readerConfig,
-                               bool enablePointerChoreographer);
     void dumpParameters(std::string& dump);
     void configureBasicParams();
     void configureOnPointerCapture(const InputReaderConfiguration& config);
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
index 987d2d0..4cd37d7 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp
@@ -33,7 +33,7 @@
 
 void ExternalStylusInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
-    if (mRawPressureAxis.valid) {
+    if (mRawPressureAxis || mTouchButtonAccumulator.hasButtonTouch()) {
         info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f,
                             0.0f, 0.0f);
     }
@@ -50,7 +50,7 @@
 std::list<NotifyArgs> ExternalStylusInputMapper::reconfigure(nsecs_t when,
                                                              const InputReaderConfiguration& config,
                                                              ConfigurationChanges changes) {
-    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPressureAxis);
+    mRawPressureAxis = getAbsoluteAxisInfo(ABS_PRESSURE);
     mTouchButtonAccumulator.configure();
     return {};
 }
@@ -61,13 +61,13 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> ExternalStylusInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> ExternalStylusInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mSingleTouchMotionAccumulator.process(rawEvent);
     mTouchButtonAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when);
     }
     return out;
 }
@@ -82,10 +82,10 @@
         mStylusState.toolType = ToolType::STYLUS;
     }
 
-    if (mRawPressureAxis.valid) {
+    if (mRawPressureAxis) {
         auto rawPressure = static_cast<float>(mSingleTouchMotionAccumulator.getAbsolutePressure());
-        mStylusState.pressure = (rawPressure - mRawPressureAxis.minValue) /
-                static_cast<float>(mRawPressureAxis.maxValue - mRawPressureAxis.minValue);
+        mStylusState.pressure = (rawPressure - mRawPressureAxis->minValue) /
+                static_cast<float>(mRawPressureAxis->maxValue - mRawPressureAxis->minValue);
     } else if (mTouchButtonAccumulator.hasButtonTouch()) {
         mStylusState.pressure = mTouchButtonAccumulator.isHovering() ? 0.0f : 1.0f;
     }
diff --git a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
index 97df02b..d48fd9b 100644
--- a/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
+++ b/services/inputflinger/reader/mapper/ExternalStylusInputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include "InputMapper.h"
 
 #include "SingleTouchMotionAccumulator.h"
@@ -39,11 +41,11 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
-    RawAbsoluteAxisInfo mRawPressureAxis;
+    std::optional<RawAbsoluteAxisInfo> mRawPressureAxis;
     TouchButtonAccumulator mTouchButtonAccumulator;
 
     StylusState mStylusState;
diff --git a/services/inputflinger/reader/mapper/InputMapper.cpp b/services/inputflinger/reader/mapper/InputMapper.cpp
index 0692dbb..627df7f 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,8 +18,11 @@
 
 #include "InputMapper.h"
 
+#include <optional>
 #include <sstream>
 
+#include <ftl/enum.h>
+
 #include "InputDevice.h"
 #include "input/PrintTools.h"
 
@@ -114,15 +117,16 @@
     return {};
 }
 
-status_t InputMapper::getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo) {
-    return getDeviceContext().getAbsoluteAxisInfo(axis, axisInfo);
+std::optional<RawAbsoluteAxisInfo> InputMapper::getAbsoluteAxisInfo(int32_t axis) {
+    return getDeviceContext().getAbsoluteAxisInfo(axis);
 }
 
 void InputMapper::bumpGeneration() {
     getDeviceContext().bumpGeneration();
 }
 
-void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+void InputMapper::dumpRawAbsoluteAxisInfo(std::string& dump,
+                                          const std::optional<RawAbsoluteAxisInfo>& axis,
                                           const char* name) {
     std::stringstream out;
     out << INDENT4 << name << ": " << axis << "\n";
@@ -133,7 +137,10 @@
     dump += StringPrintf(INDENT4 "When: %" PRId64 "\n", state.when);
     dump += StringPrintf(INDENT4 "Pressure: %s\n", toString(state.pressure).c_str());
     dump += StringPrintf(INDENT4 "Button State: 0x%08x\n", state.buttons);
-    dump += StringPrintf(INDENT4 "Tool Type: %" PRId32 "\n", state.toolType);
+    dump += StringPrintf(INDENT4 "Tool Type: %s\n", ftl::enum_string(state.toolType).c_str());
 }
 
+std::optional<HardwareProperties> InputMapper::getTouchpadHardwareProperties() {
+    return std::nullopt;
+}
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 06de4c2..75cc4bb 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <optional>
+
 #include "EventHub.h"
 #include "InputDevice.h"
 #include "InputListener.h"
@@ -78,7 +80,7 @@
                                                             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> process(const RawEvent& rawEvent) = 0;
     [[nodiscard]] virtual std::list<NotifyArgs> timeoutExpired(nsecs_t when);
 
     virtual int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode);
@@ -117,19 +119,22 @@
 
     [[nodiscard]] virtual std::list<NotifyArgs> updateExternalStylusState(const StylusState& state);
 
-    virtual std::optional<int32_t> getAssociatedDisplayId() { return std::nullopt; }
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() { return std::nullopt; }
     virtual void updateLedState(bool reset) {}
 
+    virtual std::optional<HardwareProperties> getTouchpadHardwareProperties();
+
 protected:
     InputDeviceContext& mDeviceContext;
 
     explicit InputMapper(InputDeviceContext& deviceContext,
                          const InputReaderConfiguration& readerConfig);
 
-    status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t axis);
     void bumpGeneration();
 
-    static void dumpRawAbsoluteAxisInfo(std::string& dump, const RawAbsoluteAxisInfo& axis,
+    static void dumpRawAbsoluteAxisInfo(std::string& dump,
+                                        const std::optional<RawAbsoluteAxisInfo>& axis,
                                         const char* name);
     static void dumpStylusState(std::string& dump, const StylusState& state);
 };
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
index 8a9ea75..3091714 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.cpp
@@ -117,9 +117,8 @@
                 continue; // axis must be claimed by a different device
             }
 
-            RawAbsoluteAxisInfo rawAxisInfo;
-            getAbsoluteAxisInfo(abs, &rawAxisInfo);
-            if (rawAxisInfo.valid) {
+            if (std::optional<RawAbsoluteAxisInfo> rawAxisInfo = getAbsoluteAxisInfo(abs);
+                rawAxisInfo) {
                 // Map axis.
                 AxisInfo axisInfo;
                 const bool explicitlyMapped = !getDeviceContext().mapAxis(abs, &axisInfo);
@@ -129,7 +128,7 @@
                     axisInfo.mode = AxisInfo::MODE_NORMAL;
                     axisInfo.axis = -1;
                 }
-                mAxes.insert({abs, createAxis(axisInfo, rawAxisInfo, explicitlyMapped)});
+                mAxes.insert({abs, createAxis(axisInfo, rawAxisInfo.value(), explicitlyMapped)});
             }
         }
 
@@ -259,29 +258,29 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> JoystickInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> JoystickInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_ABS: {
-            auto it = mAxes.find(rawEvent->code);
+            auto it = mAxes.find(rawEvent.code);
             if (it != mAxes.end()) {
                 Axis& axis = it->second;
                 float newValue, highNewValue;
                 switch (axis.axisInfo.mode) {
                     case AxisInfo::MODE_INVERT:
-                        newValue = (axis.rawAxisInfo.maxValue - rawEvent->value) * axis.scale +
+                        newValue = (axis.rawAxisInfo.maxValue - rawEvent.value) * axis.scale +
                                 axis.offset;
                         highNewValue = 0.0f;
                         break;
                     case AxisInfo::MODE_SPLIT:
-                        if (rawEvent->value < axis.axisInfo.splitValue) {
-                            newValue = (axis.axisInfo.splitValue - rawEvent->value) * axis.scale +
+                        if (rawEvent.value < axis.axisInfo.splitValue) {
+                            newValue = (axis.axisInfo.splitValue - rawEvent.value) * axis.scale +
                                     axis.offset;
                             highNewValue = 0.0f;
-                        } else if (rawEvent->value > axis.axisInfo.splitValue) {
+                        } else if (rawEvent.value > axis.axisInfo.splitValue) {
                             newValue = 0.0f;
                             highNewValue =
-                                    (rawEvent->value - axis.axisInfo.splitValue) * axis.highScale +
+                                    (rawEvent.value - axis.axisInfo.splitValue) * axis.highScale +
                                     axis.highOffset;
                         } else {
                             newValue = 0.0f;
@@ -289,7 +288,7 @@
                         }
                         break;
                     default:
-                        newValue = rawEvent->value * axis.scale + axis.offset;
+                        newValue = rawEvent.value * axis.scale + axis.offset;
                         highNewValue = 0.0f;
                         break;
                 }
@@ -300,9 +299,9 @@
         }
 
         case EV_SYN:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case SYN_REPORT:
-                    out += sync(rawEvent->when, rawEvent->readTime, /*force=*/false);
+                    out += sync(rawEvent.when, rawEvent.readTime, /*force=*/false);
                     break;
             }
             break;
@@ -341,7 +340,7 @@
     // button will likely wake the device.
     // TODO: Use the input device configuration to control this behavior more finely.
     uint32_t policyFlags = 0;
-    int32_t displayId = ADISPLAY_ID_NONE;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID;
     if (getDeviceContext().getAssociatedViewport()) {
         displayId = getDeviceContext().getAssociatedViewport()->displayId;
     }
diff --git a/services/inputflinger/reader/mapper/JoystickInputMapper.h b/services/inputflinger/reader/mapper/JoystickInputMapper.h
index 313f092..621d38b 100644
--- a/services/inputflinger/reader/mapper/JoystickInputMapper.h
+++ b/services/inputflinger/reader/mapper/JoystickInputMapper.h
@@ -35,7 +35,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     struct Axis {
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 738517b..38dcd65 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -21,6 +21,7 @@
 #include "KeyboardInputMapper.h"
 
 #include <ftl/enum.h>
+#include <input/KeyboardClassifier.h>
 #include <ui/Rotation.h>
 
 namespace android {
@@ -96,11 +97,11 @@
 
 KeyboardInputMapper::KeyboardInputMapper(InputDeviceContext& deviceContext,
                                          const InputReaderConfiguration& readerConfig,
-                                         uint32_t source, int32_t keyboardType)
-      : InputMapper(deviceContext, readerConfig), mSource(source), mKeyboardType(keyboardType) {}
+                                         uint32_t source)
+      : InputMapper(deviceContext, readerConfig), mMapperSource(source) {}
 
 uint32_t KeyboardInputMapper::getSources() const {
-    return mSource;
+    return mMapperSource;
 }
 
 ui::Rotation KeyboardInputMapper::getOrientation() {
@@ -110,11 +111,11 @@
     return ui::ROTATION_0;
 }
 
-int32_t KeyboardInputMapper::getDisplayId() {
+ui::LogicalDisplayId KeyboardInputMapper::getDisplayId() {
     if (mViewport) {
         return mViewport->displayId;
     }
-    return ADISPLAY_ID_NONE;
+    return ui::LogicalDisplayId::INVALID;
 }
 
 std::optional<KeyboardLayoutInfo> KeyboardInputMapper::getKeyboardLayoutInfo() const {
@@ -131,7 +132,6 @@
 void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
     InputMapper::populateDeviceInfo(info);
 
-    info.setKeyboardType(mKeyboardType);
     info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
 
     std::optional keyboardLayoutInfo = getKeyboardLayoutInfo();
@@ -143,7 +143,6 @@
 void KeyboardInputMapper::dump(std::string& dump) {
     dump += INDENT2 "Keyboard Input Mapper:\n";
     dumpParameters(dump);
-    dump += StringPrintf(INDENT3 "KeyboardType: %d\n", mKeyboardType);
     dump += StringPrintf(INDENT3 "Orientation: %s\n", ftl::enum_string(getOrientation()).c_str());
     dump += StringPrintf(INDENT3 "KeyDowns: %zu keys currently down\n", mKeyDowns.size());
     dump += StringPrintf(INDENT3 "MetaState: 0x%0x\n", mMetaState);
@@ -237,15 +236,15 @@
     return out;
 }
 
-std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    mHidUsageAccumulator.process(*rawEvent);
-    switch (rawEvent->type) {
+    mHidUsageAccumulator.process(rawEvent);
+    switch (rawEvent.type) {
         case EV_KEY: {
-            int32_t scanCode = rawEvent->code;
+            int32_t scanCode = rawEvent.code;
 
             if (isSupportedScanCode(scanCode)) {
-                out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
+                out += processKey(rawEvent.when, rawEvent.readTime, rawEvent.value != 0,
                                   scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());
             }
             break;
@@ -327,13 +326,24 @@
         keyMetaState = mMetaState;
     }
 
+    DeviceId deviceId = getDeviceId();
+
+    // On first down: Process key for keyboard classification (will send reconfiguration if the
+    // keyboard type change)
+    if (down && !keyDownIndex) {
+        KeyboardClassifier& classifier = getDeviceContext().getContext()->getKeyboardClassifier();
+        classifier.processKey(deviceId, scanCode, keyMetaState);
+        getDeviceContext().setKeyboardType(classifier.getKeyboardType(deviceId));
+    }
+
+    KeyboardType keyboardType = getDeviceContext().getKeyboardType();
     // Any key down on an external keyboard should wake the device.
     // We don't do this for internal keyboards to prevent them from waking up in your pocket.
     // For internal keyboards and devices for which the default wake behavior is explicitly
     // prevented (e.g. TV remotes), the key layout file should specify the policy flags for each
     // wake key individually.
     if (down && getDeviceContext().isExternal() && !mParameters.doNotWakeByDefault &&
-        !(mKeyboardType != AINPUT_KEYBOARD_TYPE_ALPHABETIC && isMediaKey(keyCode))) {
+        !(keyboardType != KeyboardType::ALPHABETIC && isMediaKey(keyCode))) {
         policyFlags |= POLICY_FLAG_WAKE;
     }
 
@@ -341,8 +351,8 @@
         policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
     }
 
-    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                   mSource, getDisplayId(), policyFlags,
+    out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when, readTime, deviceId,
+                                   getEventSource(), getDisplayId(), policyFlags,
                                    down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP, flags,
                                    keyCode, scanCode, keyMetaState, downTime));
     return out;
@@ -457,7 +467,7 @@
     }
 }
 
-std::optional<int32_t> KeyboardInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> KeyboardInputMapper::getAssociatedDisplayId() {
     if (mViewport) {
         return std::make_optional(mViewport->displayId);
     }
@@ -468,12 +478,12 @@
     std::list<NotifyArgs> out;
     size_t n = mKeyDowns.size();
     for (size_t i = 0; i < n; i++) {
-        out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
-                                       systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
-                                       getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP,
-                                       mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED,
-                                       mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
-                                       mKeyDowns[i].downTime));
+        out.emplace_back(
+                NotifyKeyArgs(getContext()->getNextId(), when, systemTime(SYSTEM_TIME_MONOTONIC),
+                              getDeviceId(), getEventSource(), getDisplayId(), /*policyFlags=*/0,
+                              AKEY_EVENT_ACTION_UP, mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED,
+                              mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
+                              mKeyDowns[i].downTime));
     }
     mKeyDowns.clear();
     mMetaState = AMETA_NONE;
@@ -483,18 +493,16 @@
 void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
     InputReaderContext& context = *getContext();
     context.setLastKeyDownTimestamp(downTime);
-    if (context.isPreventingTouchpadTaps()) {
-        // avoid pinging java service unnecessarily, just fade pointer again if it became visible
-        context.fadePointer();
-        return;
-    }
-    // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
-    // shortcuts
-    bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
-    if (shouldHideCursor && context.getPolicy()->isInputMethodConnectionActive()) {
-        context.fadePointer();
-        context.setPreventingTouchpadTaps(true);
-    }
+}
+
+uint32_t KeyboardInputMapper::getEventSource() const {
+    // For all input events generated by this mapper, use the source that's shared across all
+    // KeyboardInputMappers for this device in case there are more than one.
+    static constexpr auto ALL_KEYBOARD_SOURCES =
+            AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
+    const auto deviceSources = getDeviceContext().getDeviceSources();
+    LOG_ALWAYS_FATAL_IF((deviceSources & mMapperSource) != mMapperSource);
+    return deviceSources & ALL_KEYBOARD_SOURCES;
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 500256b..2df0b85 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -36,7 +36,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override;
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
@@ -46,7 +46,7 @@
 
     int32_t getMetaState() override;
     bool updateMetaState(int32_t keyCode) override;
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
     void updateLedState(bool reset) override;
 
 private:
@@ -60,8 +60,10 @@
         int32_t flags{};
     };
 
-    uint32_t mSource{};
-    int32_t mKeyboardType{};
+    // The keyboard source for this mapper. Events generated should use the source shared
+    // by all KeyboardInputMappers for this input device.
+    uint32_t mMapperSource{};
+
     std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
 
     std::vector<KeyDown> mKeyDowns{}; // keys that are down
@@ -85,13 +87,12 @@
     } mParameters{};
 
     KeyboardInputMapper(InputDeviceContext& deviceContext,
-                        const InputReaderConfiguration& readerConfig, uint32_t source,
-                        int32_t keyboardType);
+                        const InputReaderConfiguration& readerConfig, uint32_t source);
     void configureParameters();
     void dumpParameters(std::string& dump) const;
 
     ui::Rotation getOrientation();
-    int32_t getDisplayId();
+    ui::LogicalDisplayId getDisplayId();
 
     [[nodiscard]] std::list<NotifyArgs> processKey(nsecs_t when, nsecs_t readTime, bool down,
                                                    int32_t scanCode, int32_t usageCode);
@@ -108,6 +109,7 @@
     std::optional<DisplayViewport> findViewport(const InputReaderConfiguration& readerConfig);
     [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
     void onKeyDownProcessed(nsecs_t downTime);
+    uint32_t getEventSource() const;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index bca9d91..fd8224a 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -40,7 +40,7 @@
     return TouchInputMapper::reset(when);
 }
 
-std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
 
     mMultiTouchMotionAccumulator.process(rawEvent);
@@ -133,7 +133,7 @@
 
         bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
-                 (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0));
+                 (mRawPointerAxes.pressure && inSlot.getPressure() <= 0));
         outPointer.isHovering = isHovering;
 
         // Assign pointer id using tracking id if available.
@@ -189,21 +189,27 @@
 void MultiTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
-    getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x);
-    getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y);
-    getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor);
-    getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor);
-    getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor);
-    getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor);
-    getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation);
-    getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure);
-    getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance);
-    getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId);
-    getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot);
+    // TODO(b/351870641): Investigate why we are sometime not getting valid axis infos for the x/y
+    //   axes, even though those axes are required to be supported.
+    if (const auto xInfo = getAbsoluteAxisInfo(ABS_MT_POSITION_X); xInfo.has_value()) {
+        mRawPointerAxes.x = *xInfo;
+    }
+    if (const auto yInfo = getAbsoluteAxisInfo(ABS_MT_POSITION_Y); yInfo.has_value()) {
+        mRawPointerAxes.y = *yInfo;
+    }
+    mRawPointerAxes.touchMajor = getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
+    mRawPointerAxes.touchMinor = getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR);
+    mRawPointerAxes.toolMajor = getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
+    mRawPointerAxes.toolMinor = getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR);
+    mRawPointerAxes.orientation = getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+    mRawPointerAxes.pressure = getAbsoluteAxisInfo(ABS_MT_PRESSURE);
+    mRawPointerAxes.distance = getAbsoluteAxisInfo(ABS_MT_DISTANCE);
+    mRawPointerAxes.trackingId = getAbsoluteAxisInfo(ABS_MT_TRACKING_ID);
+    mRawPointerAxes.slot = getAbsoluteAxisInfo(ABS_MT_SLOT);
 
-    if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid &&
-        mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) {
-        size_t slotCount = mRawPointerAxes.slot.maxValue + 1;
+    if (mRawPointerAxes.trackingId && mRawPointerAxes.slot && mRawPointerAxes.slot->minValue == 0 &&
+        mRawPointerAxes.slot->maxValue > 0) {
+        size_t slotCount = mRawPointerAxes.slot->maxValue + 1;
         if (slotCount > MAX_SLOTS) {
             ALOGW("MultiTouch Device %s reported %zu slots but the framework "
                   "only supports a maximum of %zu slots at this time.",
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index 5c173f3..cca2327 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -31,7 +31,7 @@
     ~MultiTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index 07ae5b1..b72cc6e 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -27,11 +27,14 @@
 
 namespace android {
 
+constexpr float kDefaultScaleFactor = 1.0f;
+
 RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                                    const InputReaderConfiguration& readerConfig)
-      : InputMapper(deviceContext, readerConfig), mOrientation(ui::ROTATION_0) {
-    mSource = AINPUT_SOURCE_ROTARY_ENCODER;
-}
+      : InputMapper(deviceContext, readerConfig),
+        mSource(AINPUT_SOURCE_ROTARY_ENCODER),
+        mScalingFactor(kDefaultScaleFactor),
+        mOrientation(ui::ROTATION_0) {}
 
 RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
 
@@ -51,9 +54,10 @@
         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");
+                  "default to %f!\n",
+                  kDefaultScaleFactor);
         }
-        mScalingFactor = scalingFactor.value_or(1.0f);
+        mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor);
         info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
                             res.value_or(0.0f) * mScalingFactor);
     }
@@ -84,12 +88,18 @@
         }
     }
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        std::optional<DisplayViewport> internalViewport =
-                config.getDisplayViewportByType(ViewportType::INTERNAL);
-        if (internalViewport) {
-            mOrientation = internalViewport->orientation;
+        if (getDeviceContext().getAssociatedViewport()) {
+            mDisplayId = getDeviceContext().getAssociatedViewport()->displayId;
+            mOrientation = getDeviceContext().getAssociatedViewport()->orientation;
         } else {
-            mOrientation = ui::ROTATION_0;
+            mDisplayId = ui::LogicalDisplayId::INVALID;
+            std::optional<DisplayViewport> internalViewport =
+                    config.getDisplayViewportByType(ViewportType::INTERNAL);
+            if (internalViewport) {
+                mOrientation = internalViewport->orientation;
+            } else {
+                mOrientation = ui::ROTATION_0;
+            }
         }
     }
     return out;
@@ -101,12 +111,12 @@
     return InputMapper::reset(when);
 }
 
-std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
     mRotaryEncoderScrollAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when, rawEvent->readTime);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when, rawEvent.readTime);
     }
     return out;
 }
@@ -124,8 +134,6 @@
     // Send motion event.
     if (scrolled) {
         int32_t metaState = getContext()->getGlobalMetaState();
-        // This is not a pointer, so it's not associated with a display.
-        int32_t displayId = ADISPLAY_ID_NONE;
 
         if (mOrientation == ui::ROTATION_180) {
             scroll = -scroll;
@@ -147,7 +155,7 @@
 
         out.push_back(
                 NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                                 displayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
+                                 mDisplayId, policyFlags, AMOTION_EVENT_ACTION_SCROLL, 0, 0,
                                  metaState, /*buttonState=*/0, MotionClassification::NONE,
                                  AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
                                  &pointerCoords, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index fe5d152..7e80415 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -39,7 +39,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 private:
     CursorScrollAccumulator mRotaryEncoderScrollAccumulator;
@@ -47,6 +47,7 @@
     int32_t mSource;
     float mScalingFactor;
     ui::Rotation mOrientation;
+    ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID;
     std::unique_ptr<SlopController> mSlopController;
 
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.cpp b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
index a131e35..4233f78 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -133,9 +133,8 @@
                           .test(InputDeviceClass::SENSOR))) {
                 continue;
             }
-            RawAbsoluteAxisInfo rawAxisInfo;
-            getAbsoluteAxisInfo(abs, &rawAxisInfo);
-            if (rawAxisInfo.valid) {
+            if (std::optional<RawAbsoluteAxisInfo> rawAxisInfo = getAbsoluteAxisInfo(abs);
+                rawAxisInfo) {
                 AxisInfo axisInfo;
                 // Axis doesn't need to be mapped, as sensor mapper doesn't generate any motion
                 // input events
@@ -146,7 +145,7 @@
                 if (ret.ok()) {
                     InputDeviceSensorType sensorType = (*ret).first;
                     int32_t sensorDataIndex = (*ret).second;
-                    const Axis& axis = createAxis(axisInfo, rawAxisInfo);
+                    const Axis& axis = createAxis(axisInfo, rawAxisInfo.value());
                     parseSensorConfiguration(sensorType, abs, sensorDataIndex, axis);
 
                     mAxes.insert({abs, axis});
@@ -251,35 +250,35 @@
     mPrevMscTime = static_cast<uint32_t>(mscTime);
 }
 
-std::list<NotifyArgs> SensorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SensorInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_ABS: {
-            auto it = mAxes.find(rawEvent->code);
+            auto it = mAxes.find(rawEvent.code);
             if (it != mAxes.end()) {
                 Axis& axis = it->second;
-                axis.newValue = rawEvent->value * axis.scale + axis.offset;
+                axis.newValue = rawEvent.value * axis.scale + axis.offset;
             }
             break;
         }
 
         case EV_SYN:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case SYN_REPORT:
                     for (std::pair<const int32_t, Axis>& pair : mAxes) {
                         Axis& axis = pair.second;
                         axis.currentValue = axis.newValue;
                     }
-                    out += sync(rawEvent->when, /*force=*/false);
+                    out += sync(rawEvent.when, /*force=*/false);
                     break;
             }
             break;
 
         case EV_MSC:
-            switch (rawEvent->code) {
+            switch (rawEvent.code) {
                 case MSC_TIMESTAMP:
                     // hardware timestamp is nano seconds
-                    processHardWareTimestamp(rawEvent->when, rawEvent->value);
+                    processHardWareTimestamp(rawEvent.when, rawEvent.value);
                     break;
             }
     }
diff --git a/services/inputflinger/reader/mapper/SensorInputMapper.h b/services/inputflinger/reader/mapper/SensorInputMapper.h
index a55dcd1..63bc151 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.h
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.h
@@ -40,7 +40,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     bool enableSensor(InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
                       std::chrono::microseconds maxBatchReportLatency) override;
     void disableSensor(InputDeviceSensorType sensorType) override;
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
index ed0e270..cef1837 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp
@@ -30,7 +30,7 @@
     return TouchInputMapper::reset(when);
 }
 
-std::list<NotifyArgs> SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SingleTouchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
 
     mSingleTouchMotionAccumulator.process(rawEvent);
@@ -44,7 +44,7 @@
 
         bool isHovering = mTouchButtonAccumulator.getToolType() != ToolType::MOUSE &&
                 (mTouchButtonAccumulator.isHovering() ||
-                 (mRawPointerAxes.pressure.valid &&
+                 (mRawPointerAxes.pressure &&
                   mSingleTouchMotionAccumulator.getAbsolutePressure() <= 0));
         outState->rawPointerData.markIdBit(0, isHovering);
 
@@ -72,13 +72,19 @@
 void SingleTouchInputMapper::configureRawPointerAxes() {
     TouchInputMapper::configureRawPointerAxes();
 
-    getAbsoluteAxisInfo(ABS_X, &mRawPointerAxes.x);
-    getAbsoluteAxisInfo(ABS_Y, &mRawPointerAxes.y);
-    getAbsoluteAxisInfo(ABS_PRESSURE, &mRawPointerAxes.pressure);
-    getAbsoluteAxisInfo(ABS_TOOL_WIDTH, &mRawPointerAxes.toolMajor);
-    getAbsoluteAxisInfo(ABS_DISTANCE, &mRawPointerAxes.distance);
-    getAbsoluteAxisInfo(ABS_TILT_X, &mRawPointerAxes.tiltX);
-    getAbsoluteAxisInfo(ABS_TILT_Y, &mRawPointerAxes.tiltY);
+    // TODO(b/351870641): Investigate why we are sometime not getting valid axis infos for the x/y
+    //   axes, even though those axes are required to be supported.
+    if (const auto xInfo = getAbsoluteAxisInfo(ABS_X); xInfo.has_value()) {
+        mRawPointerAxes.x = *xInfo;
+    }
+    if (const auto yInfo = getAbsoluteAxisInfo(ABS_Y); yInfo.has_value()) {
+        mRawPointerAxes.y = *yInfo;
+    }
+    mRawPointerAxes.pressure = getAbsoluteAxisInfo(ABS_PRESSURE);
+    mRawPointerAxes.toolMajor = getAbsoluteAxisInfo(ABS_TOOL_WIDTH);
+    mRawPointerAxes.distance = getAbsoluteAxisInfo(ABS_DISTANCE);
+    mRawPointerAxes.tiltX = getAbsoluteAxisInfo(ABS_TILT_X);
+    mRawPointerAxes.tiltY = getAbsoluteAxisInfo(ABS_TILT_Y);
 }
 
 bool SingleTouchInputMapper::hasStylus() const {
diff --git a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
index 7726bfb..bc38711 100644
--- a/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SingleTouchInputMapper.h
@@ -31,7 +31,7 @@
     ~SingleTouchInputMapper() override;
 
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
 protected:
     void syncTouch(nsecs_t when, RawState* outState) override;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index 05338da..f131fb7 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
@@ -30,16 +30,16 @@
     return AINPUT_SOURCE_SWITCH;
 }
 
-std::list<NotifyArgs> SwitchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SwitchInputMapper::process(const RawEvent& rawEvent) {
     std::list<NotifyArgs> out;
-    switch (rawEvent->type) {
+    switch (rawEvent.type) {
         case EV_SW:
-            processSwitch(rawEvent->code, rawEvent->value);
+            processSwitch(rawEvent.code, rawEvent.value);
             break;
 
         case EV_SYN:
-            if (rawEvent->code == SYN_REPORT) {
-                out += sync(rawEvent->when);
+            if (rawEvent.code == SYN_REPORT) {
+                out += sync(rawEvent.when);
             }
     }
     return out;
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.h b/services/inputflinger/reader/mapper/SwitchInputMapper.h
index 2fb48bb..5d8aa2c 100644
--- a/services/inputflinger/reader/mapper/SwitchInputMapper.h
+++ b/services/inputflinger/reader/mapper/SwitchInputMapper.h
@@ -29,7 +29,7 @@
     virtual ~SwitchInputMapper();
 
     virtual uint32_t getSources() const override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode) override;
     virtual void dump(std::string& dump) override;
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
index c12e95d..d60dc55 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp
@@ -28,7 +28,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKey(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState, int32_t buttonState, int32_t keyCode) {
     std::list<NotifyArgs> out;
     if ((action == AKEY_EVENT_ACTION_DOWN && !(lastButtonState & buttonState) &&
@@ -88,7 +88,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKeys(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState) {
     std::list<NotifyArgs> out;
     out += synthesizeButtonKey(context, action, when, readTime, deviceId, source, displayId,
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 3023e68..13d952b 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -36,7 +36,7 @@
 
 [[nodiscard]] std::list<NotifyArgs> synthesizeButtonKeys(
         InputReaderContext* context, int32_t action, nsecs_t when, nsecs_t readTime,
-        int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+        int32_t deviceId, uint32_t source, ui::LogicalDisplayId displayId, uint32_t policyFlags,
         int32_t lastButtonState, int32_t currentButtonState);
 
 // For devices connected over Bluetooth, although they may produce events at a consistent rate,
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 3c26d1d..5c90cbb 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -20,8 +20,24 @@
 
 #include "TouchInputMapper.h"
 
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <tuple>
+
+#include <math.h>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
 #include <ftl/enum.h>
 #include <input/PrintTools.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <math/vec2.h>
+#include <ui/FloatRect.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
@@ -133,7 +149,10 @@
     // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified
     // by the external stylus state. That's why we don't add it directly to mSource during
     // configuration.
-    return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0);
+    return mSource |
+            (mExternalStylusPresence == ExternalStylusPresence::TOUCH_FUSION
+                     ? AINPUT_SOURCE_BLUETOOTH_STYLUS
+                     : 0);
 }
 
 void TouchInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
@@ -147,20 +166,6 @@
     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.
-        //
-        // RELATIVE_X and RELATIVE_Y motion ranges should be the largest possible relative
-        // motion, i.e. the hardware dimensions, as the finger could move completely across the
-        // 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);
-    }
-
     if (mOrientedRanges.size) {
         info.addMotionRange(*mOrientedRanges.size);
     }
@@ -268,8 +273,8 @@
     }
 
     dump += INDENT3 "Stylus Fusion:\n";
-    dump += StringPrintf(INDENT4 "ExternalStylusConnected: %s\n",
-                         toString(mExternalStylusConnected));
+    dump += StringPrintf(INDENT4 "ExternalStylusPresence: %s\n",
+                         ftl::enum_string(mExternalStylusPresence).c_str());
     dump += StringPrintf(INDENT4 "Fused External Stylus Pointer ID: %s\n",
                          toString(mFusedStylusPointerId).c_str());
     dump += StringPrintf(INDENT4 "External Stylus Data Timeout: %" PRId64 "\n",
@@ -334,7 +339,6 @@
         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
@@ -355,11 +359,19 @@
 void TouchInputMapper::resolveExternalStylusPresence() {
     std::vector<InputDeviceInfo> devices;
     getContext()->getExternalStylusDevices(devices);
-    mExternalStylusConnected = !devices.empty();
-
-    if (!mExternalStylusConnected) {
+    if (devices.empty()) {
+        mExternalStylusPresence = ExternalStylusPresence::NONE;
         resetExternalStylus();
+        return;
     }
+    mExternalStylusPresence =
+            std::any_of(devices.begin(), devices.end(),
+                        [](const auto& info) {
+                            return info.getMotionRange(AMOTION_EVENT_AXIS_PRESSURE,
+                                                       AINPUT_SOURCE_STYLUS) != nullptr;
+                        })
+            ? ExternalStylusPresence::TOUCH_FUSION
+            : ExternalStylusPresence::BUTTON_FUSION;
 }
 
 TouchInputMapper::Parameters TouchInputMapper::computeParameters(
@@ -519,7 +531,7 @@
 }
 
 bool TouchInputMapper::hasExternalStylus() const {
-    return mExternalStylusConnected;
+    return mExternalStylusPresence != ExternalStylusPresence::NONE;
 }
 
 /**
@@ -531,25 +543,19 @@
  * 4. Otherwise, use a non-display viewport.
  */
 std::optional<DisplayViewport> TouchInputMapper::findViewport() {
-    if (mParameters.hasAssociatedDisplay && mDeviceMode != DeviceMode::UNSCALED) {
+    if (mParameters.hasAssociatedDisplay) {
         if (getDeviceContext().getAssociatedViewport()) {
             return getDeviceContext().getAssociatedViewport();
         }
 
-        const std::optional<std::string> associatedDisplayUniqueId =
-                getDeviceContext().getAssociatedDisplayUniqueId();
-        if (associatedDisplayUniqueId) {
-            return getDeviceContext().getAssociatedViewport();
-        }
-
         if (mDeviceMode == DeviceMode::POINTER) {
             std::optional<DisplayViewport> viewport =
                     mConfig.getDisplayViewportById(mConfig.defaultPointerDisplayId);
             if (viewport) {
                 return viewport;
             } else {
-                ALOGW("Can't find designated display viewport with ID %" PRId32 " for pointers.",
-                      mConfig.defaultPointerDisplayId);
+                ALOGW("Can't find designated display viewport with ID %s for pointers.",
+                      mConfig.defaultPointerDisplayId.toString().c_str());
             }
         }
 
@@ -605,10 +611,10 @@
     const float diagonalSize = hypotf(mDisplayBounds.width, mDisplayBounds.height);
 
     // Size factors.
-    if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.touchMajor.maxValue != 0) {
-        mSizeScale = 1.0f / mRawPointerAxes.touchMajor.maxValue;
-    } else if (mRawPointerAxes.toolMajor.valid && mRawPointerAxes.toolMajor.maxValue != 0) {
-        mSizeScale = 1.0f / mRawPointerAxes.toolMajor.maxValue;
+    if (mRawPointerAxes.touchMajor && mRawPointerAxes.touchMajor->maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.touchMajor->maxValue;
+    } else if (mRawPointerAxes.toolMajor && mRawPointerAxes.toolMajor->maxValue != 0) {
+        mSizeScale = 1.0f / mRawPointerAxes.toolMajor->maxValue;
     } else {
         mSizeScale = 0.0f;
     }
@@ -623,18 +629,18 @@
             .resolution = 0,
     };
 
-    if (mRawPointerAxes.touchMajor.valid) {
-        mRawPointerAxes.touchMajor.resolution =
-                clampResolution("touchMajor", mRawPointerAxes.touchMajor.resolution);
-        mOrientedRanges.touchMajor->resolution = mRawPointerAxes.touchMajor.resolution;
+    if (mRawPointerAxes.touchMajor) {
+        mRawPointerAxes.touchMajor->resolution =
+                clampResolution("touchMajor", mRawPointerAxes.touchMajor->resolution);
+        mOrientedRanges.touchMajor->resolution = mRawPointerAxes.touchMajor->resolution;
     }
 
     mOrientedRanges.touchMinor = mOrientedRanges.touchMajor;
     mOrientedRanges.touchMinor->axis = AMOTION_EVENT_AXIS_TOUCH_MINOR;
-    if (mRawPointerAxes.touchMinor.valid) {
-        mRawPointerAxes.touchMinor.resolution =
-                clampResolution("touchMinor", mRawPointerAxes.touchMinor.resolution);
-        mOrientedRanges.touchMinor->resolution = mRawPointerAxes.touchMinor.resolution;
+    if (mRawPointerAxes.touchMinor) {
+        mRawPointerAxes.touchMinor->resolution =
+                clampResolution("touchMinor", mRawPointerAxes.touchMinor->resolution);
+        mOrientedRanges.touchMinor->resolution = mRawPointerAxes.touchMinor->resolution;
     }
 
     mOrientedRanges.toolMajor = InputDeviceInfo::MotionRange{
@@ -646,18 +652,18 @@
             .fuzz = 0,
             .resolution = 0,
     };
-    if (mRawPointerAxes.toolMajor.valid) {
-        mRawPointerAxes.toolMajor.resolution =
-                clampResolution("toolMajor", mRawPointerAxes.toolMajor.resolution);
-        mOrientedRanges.toolMajor->resolution = mRawPointerAxes.toolMajor.resolution;
+    if (mRawPointerAxes.toolMajor) {
+        mRawPointerAxes.toolMajor->resolution =
+                clampResolution("toolMajor", mRawPointerAxes.toolMajor->resolution);
+        mOrientedRanges.toolMajor->resolution = mRawPointerAxes.toolMajor->resolution;
     }
 
     mOrientedRanges.toolMinor = mOrientedRanges.toolMajor;
     mOrientedRanges.toolMinor->axis = AMOTION_EVENT_AXIS_TOOL_MINOR;
-    if (mRawPointerAxes.toolMinor.valid) {
-        mRawPointerAxes.toolMinor.resolution =
-                clampResolution("toolMinor", mRawPointerAxes.toolMinor.resolution);
-        mOrientedRanges.toolMinor->resolution = mRawPointerAxes.toolMinor.resolution;
+    if (mRawPointerAxes.toolMinor) {
+        mRawPointerAxes.toolMinor->resolution =
+                clampResolution("toolMinor", mRawPointerAxes.toolMinor->resolution);
+        mOrientedRanges.toolMinor->resolution = mRawPointerAxes.toolMinor->resolution;
     }
 
     if (mCalibration.sizeCalibration == Calibration::SizeCalibration::GEOMETRIC) {
@@ -709,9 +715,10 @@
         mCalibration.pressureCalibration == Calibration::PressureCalibration::AMPLITUDE) {
         if (mCalibration.pressureScale) {
             mPressureScale = *mCalibration.pressureScale;
-            pressureMax = mPressureScale * mRawPointerAxes.pressure.maxValue;
-        } else if (mRawPointerAxes.pressure.valid && mRawPointerAxes.pressure.maxValue != 0) {
-            mPressureScale = 1.0f / mRawPointerAxes.pressure.maxValue;
+            pressureMax = mPressureScale *
+                    (mRawPointerAxes.pressure ? mRawPointerAxes.pressure->maxValue : 0);
+        } else if (mRawPointerAxes.pressure && mRawPointerAxes.pressure->maxValue != 0) {
+            mPressureScale = 1.0f / mRawPointerAxes.pressure->maxValue;
         }
     }
 
@@ -730,18 +737,18 @@
     mTiltXScale = 0;
     mTiltYCenter = 0;
     mTiltYScale = 0;
-    mHaveTilt = mRawPointerAxes.tiltX.valid && mRawPointerAxes.tiltY.valid;
+    mHaveTilt = mRawPointerAxes.tiltX && mRawPointerAxes.tiltY;
     if (mHaveTilt) {
-        mTiltXCenter = avg(mRawPointerAxes.tiltX.minValue, mRawPointerAxes.tiltX.maxValue);
-        mTiltYCenter = avg(mRawPointerAxes.tiltY.minValue, mRawPointerAxes.tiltY.maxValue);
+        mTiltXCenter = avg(mRawPointerAxes.tiltX->minValue, mRawPointerAxes.tiltX->maxValue);
+        mTiltYCenter = avg(mRawPointerAxes.tiltY->minValue, mRawPointerAxes.tiltY->maxValue);
         mTiltXScale = M_PI / 180;
         mTiltYScale = M_PI / 180;
 
-        if (mRawPointerAxes.tiltX.resolution) {
-            mTiltXScale = 1.0 / mRawPointerAxes.tiltX.resolution;
+        if (mRawPointerAxes.tiltX->resolution) {
+            mTiltXScale = 1.0 / mRawPointerAxes.tiltX->resolution;
         }
-        if (mRawPointerAxes.tiltY.resolution) {
-            mTiltYScale = 1.0 / mRawPointerAxes.tiltY.resolution;
+        if (mRawPointerAxes.tiltY->resolution) {
+            mTiltYScale = 1.0 / mRawPointerAxes.tiltY->resolution;
         }
 
         mOrientedRanges.tilt = InputDeviceInfo::MotionRange{
@@ -771,11 +778,11 @@
     } else if (mCalibration.orientationCalibration != Calibration::OrientationCalibration::NONE) {
         if (mCalibration.orientationCalibration ==
             Calibration::OrientationCalibration::INTERPOLATED) {
-            if (mRawPointerAxes.orientation.valid) {
-                if (mRawPointerAxes.orientation.maxValue > 0) {
-                    mOrientationScale = M_PI_2 / mRawPointerAxes.orientation.maxValue;
-                } else if (mRawPointerAxes.orientation.minValue < 0) {
-                    mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation.minValue;
+            if (mRawPointerAxes.orientation) {
+                if (mRawPointerAxes.orientation->maxValue > 0) {
+                    mOrientationScale = M_PI_2 / mRawPointerAxes.orientation->maxValue;
+                } else if (mRawPointerAxes.orientation->minValue < 0) {
+                    mOrientationScale = -M_PI_2 / mRawPointerAxes.orientation->minValue;
                 } else {
                     mOrientationScale = 0;
                 }
@@ -800,14 +807,14 @@
             mDistanceScale = mCalibration.distanceScale.value_or(1.0f);
         }
 
+        const bool hasDistance = mRawPointerAxes.distance.has_value();
         mOrientedRanges.distance = InputDeviceInfo::MotionRange{
-
                 .axis = AMOTION_EVENT_AXIS_DISTANCE,
                 .source = mSource,
-                .min = mRawPointerAxes.distance.minValue * mDistanceScale,
-                .max = mRawPointerAxes.distance.maxValue * mDistanceScale,
+                .min = hasDistance ? mRawPointerAxes.distance->minValue * mDistanceScale : 0,
+                .max = hasDistance ? mRawPointerAxes.distance->maxValue * mDistanceScale : 0,
                 .flat = 0,
-                .fuzz = mRawPointerAxes.distance.fuzz * mDistanceScale,
+                .fuzz = hasDistance ? mRawPointerAxes.distance->fuzz * mDistanceScale : 0,
                 .resolution = 0,
         };
     }
@@ -923,7 +930,7 @@
 
     // Determine device mode.
     if (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-        mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.enable) {
+        mConfig.pointerGesturesEnabled && !mConfig.pointerCaptureRequest.isEnable()) {
         mSource = AINPUT_SOURCE_MOUSE;
         mDeviceMode = DeviceMode::POINTER;
         if (hasStylus()) {
@@ -936,22 +943,19 @@
             mSource |= AINPUT_SOURCE_STYLUS;
         }
     } else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
-        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
+        mSource = AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD;
         mDeviceMode = DeviceMode::NAVIGATION;
     } else {
-        mSource = AINPUT_SOURCE_TOUCHPAD;
-        mDeviceMode = DeviceMode::UNSCALED;
+        ALOGW("Touch device '%s' has invalid parameters or configuration.  The device will be "
+              "inoperable.",
+              getDeviceName().c_str());
+        mDeviceMode = DeviceMode::DISABLED;
     }
 
     const std::optional<DisplayViewport> newViewportOpt = findViewport();
 
     // Ensure the device is valid and can be used.
-    if (!mRawPointerAxes.x.valid || !mRawPointerAxes.y.valid) {
-        ALOGW("Touch device '%s' did not report support for X or Y axis!  "
-              "The device will be inoperable.",
-              getDeviceName().c_str());
-        mDeviceMode = DeviceMode::DISABLED;
-    } else if (!newViewportOpt) {
+    if (!newViewportOpt) {
         ALOGI("Touch device '%s' could not query the properties of its associated "
               "display.  The device will be inoperable until the display size "
               "becomes available.",
@@ -984,8 +988,9 @@
         viewportChanged = mViewport != newViewport;
     }
 
+    const bool deviceModeChanged = mDeviceMode != oldDeviceMode;
     bool skipViewportUpdate = false;
-    if (viewportChanged) {
+    if (viewportChanged || deviceModeChanged) {
         const bool viewportOrientationChanged = mViewport.orientation != newViewport.orientation;
         const bool viewportDisplayIdChanged = mViewport.displayId != newViewport.displayId;
         mViewport = newViewport;
@@ -1027,42 +1032,16 @@
     }
 
     // If moving between pointer modes, need to reset some state.
-    bool deviceModeChanged = mDeviceMode != oldDeviceMode;
     if (deviceModeChanged) {
         mOrientedRanges.clear();
     }
 
-    // Create and preserve the pointer controller in the following cases:
-    const bool isPointerControllerNeeded =
-            // - when the device is in pointer mode, to show the mouse cursor;
-            (mDeviceMode == DeviceMode::POINTER) ||
-            // - when pointer capture is enabled, to preserve the mouse cursor position;
-            (mParameters.deviceType == Parameters::DeviceType::POINTER &&
-             mConfig.pointerCaptureRequest.enable) ||
-            // - when we should be showing touches;
-            (mDeviceMode == DeviceMode::DIRECT && mConfig.showTouches) ||
-            // - when we should be showing a pointer icon for direct styluses.
-            (mDeviceMode == DeviceMode::DIRECT && mConfig.stylusPointerIconEnabled && hasStylus());
-    if (isPointerControllerNeeded) {
-        if (mPointerController == nullptr) {
-            mPointerController = getContext()->getPointerController(getDeviceId());
-        }
-        if (mConfig.pointerCaptureRequest.enable) {
-            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-        }
-    } else {
-        if (mPointerController != nullptr && mDeviceMode == DeviceMode::DIRECT &&
-            !mConfig.showTouches) {
-            mPointerController->clearSpots();
-        }
-        mPointerController.reset();
-    }
-
     if ((viewportChanged && !skipViewportUpdate) || deviceModeChanged) {
-        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %d, mode %d, "
-              "display id %d",
+        ALOGI("Device reconfigured: id=%d, name='%s', size %s, orientation %s, mode %s, "
+              "display id %s",
               getDeviceId(), getDeviceName().c_str(), toString(mDisplayBounds).c_str(),
-              mInputDeviceOrientation, mDeviceMode, mViewport.displayId);
+              ftl::enum_string(mInputDeviceOrientation).c_str(),
+              ftl::enum_string(mDeviceMode).c_str(), mViewport.displayId.toString().c_str());
 
         configureVirtualKeys();
 
@@ -1113,7 +1092,8 @@
     dump += StringPrintf(INDENT3 "DisplayBounds: %s\n", toString(mDisplayBounds).c_str());
     dump += StringPrintf(INDENT3 "PhysicalFrameInRotatedDisplay: %s\n",
                          toString(mPhysicalFrameInRotatedDisplay).c_str());
-    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %d\n", mInputDeviceOrientation);
+    dump += StringPrintf(INDENT3 "InputDeviceOrientation: %s\n",
+                         ftl::enum_string(mInputDeviceOrientation).c_str());
 }
 
 void TouchInputMapper::configureVirtualKeys() {
@@ -1264,7 +1244,7 @@
 
 void TouchInputMapper::resolveCalibration() {
     // Size
-    if (mRawPointerAxes.touchMajor.valid || mRawPointerAxes.toolMajor.valid) {
+    if (mRawPointerAxes.touchMajor || mRawPointerAxes.toolMajor) {
         if (mCalibration.sizeCalibration == Calibration::SizeCalibration::DEFAULT) {
             mCalibration.sizeCalibration = Calibration::SizeCalibration::GEOMETRIC;
         }
@@ -1273,7 +1253,7 @@
     }
 
     // Pressure
-    if (mRawPointerAxes.pressure.valid) {
+    if (mRawPointerAxes.pressure) {
         if (mCalibration.pressureCalibration == Calibration::PressureCalibration::DEFAULT) {
             mCalibration.pressureCalibration = Calibration::PressureCalibration::PHYSICAL;
         }
@@ -1282,7 +1262,7 @@
     }
 
     // Orientation
-    if (mRawPointerAxes.orientation.valid) {
+    if (mRawPointerAxes.orientation) {
         if (mCalibration.orientationCalibration == Calibration::OrientationCalibration::DEFAULT) {
             mCalibration.orientationCalibration = Calibration::OrientationCalibration::INTERPOLATED;
         }
@@ -1291,7 +1271,7 @@
     }
 
     // Distance
-    if (mRawPointerAxes.distance.valid) {
+    if (mRawPointerAxes.distance) {
         if (mCalibration.distanceCalibration == Calibration::DistanceCalibration::DEFAULT) {
             mCalibration.distanceCalibration = Calibration::DistanceCalibration::SCALED;
         }
@@ -1388,7 +1368,6 @@
 
 std::list<NotifyArgs> TouchInputMapper::reset(nsecs_t when) {
     std::list<NotifyArgs> out = cancelTouch(when, when);
-    updateTouchSpots();
 
     mCursorButtonAccumulator.reset(getDeviceContext());
     mCursorScrollAccumulator.reset(getDeviceContext());
@@ -1415,11 +1394,6 @@
     mPointerSimple.reset();
     resetExternalStylus();
 
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        mPointerController->clearSpots();
-    }
-
     return out += InputMapper::reset(when);
 }
 
@@ -1436,14 +1410,14 @@
     mExternalStylusFusionTimeout = LLONG_MAX;
 }
 
-std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> TouchInputMapper::process(const RawEvent& rawEvent) {
     mCursorButtonAccumulator.process(rawEvent);
     mCursorScrollAccumulator.process(rawEvent);
     mTouchButtonAccumulator.process(rawEvent);
 
     std::list<NotifyArgs> out;
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out += sync(rawEvent->when, rawEvent->readTime);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out += sync(rawEvent.when, rawEvent.readTime);
     }
     return out;
 }
@@ -1574,11 +1548,6 @@
     uint32_t policyFlags = 0;
     bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState;
     if (initialDown || buttonsPressed) {
-        // If this is a touch screen, hide the pointer on an initial down.
-        if (mDeviceMode == DeviceMode::DIRECT) {
-            getContext()->fadePointer();
-        }
-
         if (mParameters.wake) {
             policyFlags |= POLICY_FLAG_WAKE;
         }
@@ -1646,7 +1615,6 @@
         out += dispatchPointerUsage(when, readTime, policyFlags, pointerUsage);
     } else {
         if (!mCurrentMotionAborted) {
-            updateTouchSpots();
             out += dispatchButtonRelease(when, readTime, policyFlags);
             out += dispatchHoverExit(when, readTime, policyFlags);
             out += dispatchTouches(when, readTime, policyFlags);
@@ -1678,28 +1646,6 @@
     return out;
 }
 
-void TouchInputMapper::updateTouchSpots() {
-    if (!mConfig.showTouches || mPointerController == nullptr) {
-        return;
-    }
-
-    // Update touch spots when this is a touchscreen even when it's not enabled so that we can
-    // clear touch spots.
-    if (mDeviceMode != DeviceMode::DIRECT &&
-        (mDeviceMode != DeviceMode::DISABLED || !isTouchScreen())) {
-        return;
-    }
-
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::SPOT);
-    mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-
-    mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords.cbegin(),
-                                 mCurrentCookedState.cookedPointerData.idToIndex.cbegin(),
-                                 mCurrentCookedState.cookedPointerData.touchingIdBits |
-                                         mCurrentCookedState.cookedPointerData.hoveringIdBits,
-                                 mViewport.displayId);
-}
-
 bool TouchInputMapper::isTouchScreen() {
     return mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN &&
             mParameters.hasAssociatedDisplay;
@@ -1884,8 +1830,7 @@
     }
 
     if (!mCurrentRawState.rawPointerData.hoveringIdBits.isEmpty() &&
-        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty() &&
-        mDeviceMode != DeviceMode::UNSCALED) {
+        mCurrentRawState.rawPointerData.touchingIdBits.isEmpty()) {
         // We have hovering pointers, and there are no touching pointers.
         bool hoveringPointersInFrame = false;
         auto hoveringIds = mCurrentRawState.rawPointerData.hoveringIdBits;
@@ -1912,7 +1857,7 @@
         // Skip checking whether the pointer is inside the physical frame if the device is in
         // unscaled or pointer mode.
         if (!isPointInsidePhysicalFrame(pointer.x, pointer.y) &&
-            mDeviceMode != DeviceMode::UNSCALED && mDeviceMode != DeviceMode::POINTER) {
+            mDeviceMode != DeviceMode::POINTER) {
             // If exactly one pointer went down, check for virtual key hit.
             // Otherwise, we will drop the entire stroke.
             if (mCurrentRawState.rawPointerData.touchingIdBits.count() == 1) {
@@ -2313,25 +2258,25 @@
             case Calibration::SizeCalibration::DIAMETER:
             case Calibration::SizeCalibration::BOX:
             case Calibration::SizeCalibration::AREA:
-                if (mRawPointerAxes.touchMajor.valid && mRawPointerAxes.toolMajor.valid) {
+                if (mRawPointerAxes.touchMajor && mRawPointerAxes.toolMajor) {
                     touchMajor = in.touchMajor;
-                    touchMinor = mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
+                    touchMinor = mRawPointerAxes.touchMinor ? in.touchMinor : in.touchMajor;
                     toolMajor = in.toolMajor;
-                    toolMinor = mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
-                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
-                                                            : in.touchMajor;
-                } else if (mRawPointerAxes.touchMajor.valid) {
+                    toolMinor = mRawPointerAxes.toolMinor ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.touchMinor ? avg(in.touchMajor, in.touchMinor)
+                                                      : in.touchMajor;
+                } else if (mRawPointerAxes.touchMajor) {
                     toolMajor = touchMajor = in.touchMajor;
                     toolMinor = touchMinor =
-                            mRawPointerAxes.touchMinor.valid ? in.touchMinor : in.touchMajor;
-                    size = mRawPointerAxes.touchMinor.valid ? avg(in.touchMajor, in.touchMinor)
-                                                            : in.touchMajor;
-                } else if (mRawPointerAxes.toolMajor.valid) {
+                            mRawPointerAxes.touchMinor ? in.touchMinor : in.touchMajor;
+                    size = mRawPointerAxes.touchMinor ? avg(in.touchMajor, in.touchMinor)
+                                                      : in.touchMajor;
+                } else if (mRawPointerAxes.toolMajor) {
                     touchMajor = toolMajor = in.toolMajor;
                     touchMinor = toolMinor =
-                            mRawPointerAxes.toolMinor.valid ? in.toolMinor : in.toolMajor;
-                    size = mRawPointerAxes.toolMinor.valid ? avg(in.toolMajor, in.toolMinor)
-                                                           : in.toolMajor;
+                            mRawPointerAxes.toolMinor ? in.toolMinor : in.toolMajor;
+                    size = mRawPointerAxes.toolMinor ? avg(in.toolMajor, in.toolMinor)
+                                                     : in.toolMajor;
                 } else {
                     ALOG_ASSERT(false,
                                 "No touch or tool axes.  "
@@ -2405,20 +2350,23 @@
         if (mHaveTilt) {
             float tiltXAngle = (in.tiltX - mTiltXCenter) * mTiltXScale;
             float tiltYAngle = (in.tiltY - mTiltYCenter) * mTiltYScale;
-            orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)));
+            orientation = transformAngle(mRawRotation, atan2f(-sinf(tiltXAngle), sinf(tiltYAngle)),
+                                         /*isDirectional=*/true);
             tilt = acosf(cosf(tiltXAngle) * cosf(tiltYAngle));
         } else {
             tilt = 0;
 
             switch (mCalibration.orientationCalibration) {
                 case Calibration::OrientationCalibration::INTERPOLATED:
-                    orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale);
+                    orientation = transformAngle(mRawRotation, in.orientation * mOrientationScale,
+                                                 /*isDirectional=*/true);
                     break;
                 case Calibration::OrientationCalibration::VECTOR: {
                     int32_t c1 = signExtendNybble((in.orientation & 0xf0) >> 4);
                     int32_t c2 = signExtendNybble(in.orientation & 0x0f);
                     if (c1 != 0 || c2 != 0) {
-                        orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f);
+                        orientation = transformAngle(mRawRotation, atan2f(c1, c2) * 0.5f,
+                                                     /*isDirectional=*/true);
                         float confidence = hypotf(c1, c2);
                         float scale = 1.0f + confidence / 16.0f;
                         touchMajor *= scale;
@@ -2549,54 +2497,6 @@
         cancelPreviousGesture = false;
     }
 
-    // Update the pointer presentation and spots.
-    if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) {
-        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-        if (finishPreviousGesture || cancelPreviousGesture) {
-            mPointerController->clearSpots();
-        }
-
-        if (mPointerGesture.currentGestureMode == PointerGesture::Mode::FREEFORM) {
-            mPointerController->setSpots(mPointerGesture.currentGestureCoords.cbegin(),
-                                         mPointerGesture.currentGestureIdToIndex.cbegin(),
-                                         mPointerGesture.currentGestureIdBits,
-                                         mPointerController->getDisplayId());
-        }
-    } else {
-        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    }
-
-    // Show or hide the pointer if needed.
-    switch (mPointerGesture.currentGestureMode) {
-        case PointerGesture::Mode::NEUTRAL:
-        case PointerGesture::Mode::QUIET:
-            if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH &&
-                mPointerGesture.lastGestureMode == PointerGesture::Mode::FREEFORM) {
-                // Remind the user of where the pointer is after finishing a gesture with spots.
-                mPointerController->unfade(PointerControllerInterface::Transition::GRADUAL);
-            }
-            break;
-        case PointerGesture::Mode::TAP:
-        case PointerGesture::Mode::TAP_DRAG:
-        case PointerGesture::Mode::BUTTON_CLICK_OR_DRAG:
-        case PointerGesture::Mode::HOVER:
-        case PointerGesture::Mode::PRESS:
-        case PointerGesture::Mode::SWIPE:
-            // Unfade the pointer when the current gesture manipulates the
-            // area directly under the pointer.
-            mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            break;
-        case PointerGesture::Mode::FREEFORM:
-            // Fade the pointer when the current gesture manipulates a different
-            // area and there are spots to guide the user experience.
-            if (mParameters.gestureMode == Parameters::GestureMode::MULTI_TOUCH) {
-                mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-            } else {
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-            }
-            break;
-    }
-
     // Send events!
     int32_t metaState = getContext()->getGlobalMetaState();
     int32_t buttonState = mCurrentCookedState.buttonState;
@@ -2735,7 +2635,6 @@
         // 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.
-        const auto [x, y] = mPointerController->getPosition();
 
         PointerProperties pointerProperties;
         pointerProperties.clear();
@@ -2744,16 +2643,12 @@
 
         PointerCoords pointerCoords;
         pointerCoords.clear();
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
-
-        const int32_t displayId = mPointerController->getDisplayId();
         out.push_back(NotifyMotionArgs(getContext()->getNextId(), when, readTime, getDeviceId(),
-                                       mSource, displayId, policyFlags,
+                                       mSource, ui::LogicalDisplayId::INVALID, policyFlags,
                                        AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags, metaState,
                                        buttonState, MotionClassification::NONE,
                                        AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties,
-                                       &pointerCoords, 0, 0, x, y, mPointerGesture.downTime,
+                                       &pointerCoords, 0, 0, 0.f, 0.f, mPointerGesture.downTime,
                                        /*videoFrames=*/{}));
     }
 
@@ -2799,12 +2694,6 @@
     // Reset the current pointer gesture.
     mPointerGesture.reset();
     mPointerVelocityControl.reset();
-
-    // Remove any current spots.
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        mPointerController->clearSpots();
-    }
     return out;
 }
 
@@ -2940,8 +2829,6 @@
             mPointerVelocityControl.reset();
         }
 
-        const auto [x, y] = mPointerController->getPosition();
-
         mPointerGesture.currentGestureMode = PointerGesture::Mode::BUTTON_CLICK_OR_DRAG;
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
@@ -2950,8 +2837,6 @@
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
         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);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
     } else if (currentFingerCount == 0) {
         // Case 3. No fingers down and button is not pressed. (NEUTRAL)
@@ -2966,9 +2851,8 @@
              mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP_DRAG) &&
             lastFingerCount == 1) {
             if (when <= mPointerGesture.tapDownTime + mConfig.pointerGestureTapInterval) {
-                const auto [x, y] = mPointerController->getPosition();
-                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
-                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+                if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: TAP");
 
                     mPointerGesture.tapUpTime = when;
@@ -2995,7 +2879,7 @@
                     tapped = true;
                 } else {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP, deltaX=%f, deltaY=%f",
-                             x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+                             0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY);
                 }
             } else {
                 if (DEBUG_GESTURES) {
@@ -3027,13 +2911,12 @@
         mPointerGesture.currentGestureMode = PointerGesture::Mode::HOVER;
         if (mPointerGesture.lastGestureMode == PointerGesture::Mode::TAP) {
             if (when <= mPointerGesture.tapUpTime + mConfig.pointerGestureTapDragInterval) {
-                const auto [x, y] = mPointerController->getPosition();
-                if (fabs(x - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
-                    fabs(y - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
+                if (fabs(0.f - mPointerGesture.tapX) <= mConfig.pointerGestureTapSlop &&
+                    fabs(0.f - mPointerGesture.tapY) <= mConfig.pointerGestureTapSlop) {
                     mPointerGesture.currentGestureMode = PointerGesture::Mode::TAP_DRAG;
                 } else {
                     ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
-                             x - mPointerGesture.tapX, y - mPointerGesture.tapY);
+                             0.f - mPointerGesture.tapX, 0.f - mPointerGesture.tapY);
                 }
             } else {
                 ALOGD_IF(DEBUG_GESTURES, "Gestures: Not a TAP_DRAG, %0.3fms time since up",
@@ -3063,8 +2946,6 @@
             down = false;
         }
 
-        const auto [x, y] = mPointerController->getPosition();
-
         mPointerGesture.currentGestureIdBits.clear();
         mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
         mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3072,16 +2953,14 @@
         mPointerGesture.currentGestureProperties[0].id = mPointerGesture.activeGestureId;
         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);
         mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
                                                              down ? 1.0f : 0.0f);
 
         if (lastFingerCount == 0 && currentFingerCount != 0) {
             mPointerGesture.resetTap();
             mPointerGesture.tapDownTime = when;
-            mPointerGesture.tapX = x;
-            mPointerGesture.tapY = y;
+            mPointerGesture.tapX = 0.f;
+            mPointerGesture.tapY = 0.f;
         }
     } else {
         // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
@@ -3090,11 +2969,13 @@
 
     if (DEBUG_GESTURES) {
         ALOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
-              "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
-              "lastGestureMode=%d, lastGestureIdBits=0x%08x",
+              "currentGestureMode=%s, currentGestureIdBits=0x%08x, "
+              "lastGestureMode=%s, lastGestureIdBits=0x%08x",
               toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
-              mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
-              mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
+              ftl::enum_string(mPointerGesture.currentGestureMode).c_str(),
+              mPointerGesture.currentGestureIdBits.value,
+              ftl::enum_string(mPointerGesture.lastGestureMode).c_str(),
+              mPointerGesture.lastGestureIdBits.value);
         for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
@@ -3230,8 +3111,8 @@
         mCurrentRawState.rawPointerData
                 .getCentroidOfTouchingPointers(&mPointerGesture.referenceTouchX,
                                                &mPointerGesture.referenceTouchY);
-        std::tie(mPointerGesture.referenceGestureX, mPointerGesture.referenceGestureY) =
-                mPointerController->getPosition();
+        mPointerGesture.referenceGestureX = 0.f;
+        mPointerGesture.referenceGestureY = 0.f;
     }
 
     // Clear the reference deltas for fingers not yet included in the reference calculation.
@@ -3526,8 +3407,6 @@
 
     rotateDelta(mInputDeviceOrientation, &deltaX, &deltaY);
     mPointerVelocityControl.move(when, &deltaX, &deltaY);
-
-    mPointerController->move(deltaX, deltaY);
 }
 
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime,
@@ -3544,13 +3423,6 @@
 
         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 = mCurrentCookedState.cookedPointerData.pointerCoords[index];
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -3588,12 +3460,9 @@
         down = isPointerDown(mCurrentRawState.buttonState);
         hovering = !down;
 
-        const auto [x, y] = mPointerController->getPosition();
         const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
         mPointerSimple.currentCoords =
                 mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex];
-        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
-        mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
         mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
                                                   hovering ? 0.0f : 1.0f);
         mPointerSimple.currentProperties.id = 0;
@@ -3606,8 +3475,8 @@
         hovering = false;
     }
 
-    const int32_t displayId = mPointerController->getDisplayId();
-    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering, displayId);
+    return dispatchPointerSimple(when, readTime, policyFlags, down, hovering,
+                                 ui::LogicalDisplayId::INVALID);
 }
 
 std::list<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime,
@@ -3621,24 +3490,14 @@
 
 std::list<NotifyArgs> TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering, int32_t displayId) {
+                                                              bool hovering,
+                                                              ui::LogicalDisplayId 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 (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);
-        }
-    }
-
     if (mPointerSimple.down && !down) {
         mPointerSimple.down = false;
 
@@ -3774,9 +3633,6 @@
                                        mPointerSimple.lastCursorX, mPointerSimple.lastCursorY,
                                        mPointerSimple.downTime,
                                        /*videoFrames=*/{}));
-        if (mPointerController != nullptr) {
-            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-        }
     }
     mPointerSimple.reset();
     return out;
@@ -3826,36 +3682,24 @@
     if (mCurrentStreamModifiedByExternalStylus) {
         source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
     }
-
-    const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
-    const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
-            mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) &&
-            mPointerController && displayId != ADISPLAY_ID_NONE &&
-            displayId == mPointerController->getDisplayId();
-    if (showDirectStylusPointer) {
-        switch (action & AMOTION_EVENT_ACTION_MASK) {
-            case AMOTION_EVENT_ACTION_HOVER_ENTER:
-            case AMOTION_EVENT_ACTION_HOVER_MOVE:
-                mPointerController->setPresentation(
-                        PointerControllerInterface::Presentation::STYLUS_HOVER);
-                mPointerController
-                        ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(),
-                                      mCurrentCookedState.cookedPointerData.pointerCoords[0]
-                                              .getY());
-                mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-                break;
-            case AMOTION_EVENT_ACTION_HOVER_EXIT:
-                mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-                break;
+    if (mOrientedRanges.orientation.has_value()) {
+        flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION;
+        if (mOrientedRanges.tilt.has_value()) {
+            // In the current implementation, only devices that report a value for tilt supports
+            // directional orientation.
+            flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
         }
     }
 
+    const ui::LogicalDisplayId displayId =
+            getAssociatedDisplayId().value_or(ui::LogicalDisplayId::INVALID);
+
     float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
     if (mDeviceMode == DeviceMode::POINTER) {
-        std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
+        xCursorPosition = yCursorPosition = 0.f;
     }
-    const int32_t deviceId = getDeviceId();
+    const DeviceId deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
     std::for_each(frames.begin(), frames.end(),
                   [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
@@ -4122,10 +3966,10 @@
     return true;
 }
 
-std::optional<int32_t> TouchInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchInputMapper::getAssociatedDisplayId() {
     if (mParameters.hasAssociatedDisplay) {
         if (mDeviceMode == DeviceMode::POINTER) {
-            return std::make_optional(mPointerController->getDisplayId());
+            return ui::LogicalDisplayId::INVALID;
         } else {
             return std::make_optional(mViewport.displayId);
         }
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 4b39e40..ef0e02f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -16,17 +16,36 @@
 
 #pragma once
 
+#include <array>
+#include <climits>
+#include <limits>
+#include <list>
+#include <memory>
 #include <optional>
 #include <string>
+#include <utility>
+#include <vector>
 
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/VelocityControl.h>
+#include <input/VelocityTracker.h>
 #include <stdint.h>
+#include <ui/Rect.h>
 #include <ui/Rotation.h>
+#include <ui/Size.h>
+#include <ui/Transform.h>
+#include <utils/BitSet.h>
+#include <utils/Timers.h>
 
 #include "CursorButtonAccumulator.h"
 #include "CursorScrollAccumulator.h"
 #include "EventHub.h"
 #include "InputMapper.h"
 #include "InputReaderBase.h"
+#include "NotifyArgs.h"
+#include "StylusState.h"
 #include "TouchButtonAccumulator.h"
 
 namespace android {
@@ -42,17 +61,17 @@
 struct RawPointerAxes {
     RawAbsoluteAxisInfo x{};
     RawAbsoluteAxisInfo y{};
-    RawAbsoluteAxisInfo pressure{};
-    RawAbsoluteAxisInfo touchMajor{};
-    RawAbsoluteAxisInfo touchMinor{};
-    RawAbsoluteAxisInfo toolMajor{};
-    RawAbsoluteAxisInfo toolMinor{};
-    RawAbsoluteAxisInfo orientation{};
-    RawAbsoluteAxisInfo distance{};
-    RawAbsoluteAxisInfo tiltX{};
-    RawAbsoluteAxisInfo tiltY{};
-    RawAbsoluteAxisInfo trackingId{};
-    RawAbsoluteAxisInfo slot{};
+    std::optional<RawAbsoluteAxisInfo> pressure{};
+    std::optional<RawAbsoluteAxisInfo> touchMajor{};
+    std::optional<RawAbsoluteAxisInfo> touchMinor{};
+    std::optional<RawAbsoluteAxisInfo> toolMajor{};
+    std::optional<RawAbsoluteAxisInfo> toolMinor{};
+    std::optional<RawAbsoluteAxisInfo> orientation{};
+    std::optional<RawAbsoluteAxisInfo> distance{};
+    std::optional<RawAbsoluteAxisInfo> tiltX{};
+    std::optional<RawAbsoluteAxisInfo> tiltY{};
+    std::optional<RawAbsoluteAxisInfo> trackingId{};
+    std::optional<RawAbsoluteAxisInfo> slot{};
 
     inline int32_t getRawWidth() const { return x.maxValue - x.minValue + 1; }
     inline int32_t getRawHeight() const { return y.maxValue - y.minValue + 1; }
@@ -155,7 +174,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     int32_t getKeyCodeState(uint32_t sourceMask, int32_t keyCode) override;
     int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
@@ -166,7 +185,7 @@
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> updateExternalStylusState(
             const StylusState& state) override;
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
 
 protected:
     CursorButtonAccumulator mCursorButtonAccumulator;
@@ -195,7 +214,6 @@
     enum class DeviceMode {
         DISABLED,   // input is disabled
         DIRECT,     // direct mapping (touchscreen)
-        UNSCALED,   // unscaled mapping (e.g. captured touchpad)
         NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
         POINTER,    // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
 
@@ -321,8 +339,8 @@
         int32_t buttonState{};
 
         // Scroll state.
-        int32_t rawVScroll{};
-        int32_t rawHScroll{};
+        float rawVScroll{};
+        float rawHScroll{};
 
         inline void clear() { *this = RawState(); }
     };
@@ -347,6 +365,16 @@
     RawState mLastRawState;
     CookedState mLastCookedState;
 
+    enum class ExternalStylusPresence {
+        // No external stylus connected.
+        NONE,
+        // An external stylus that can report touch/pressure that can be fused with the touchscreen.
+        TOUCH_FUSION,
+        // An external stylus that can only report buttons.
+        BUTTON_FUSION,
+        ftl_last = BUTTON_FUSION,
+    };
+    ExternalStylusPresence mExternalStylusPresence{ExternalStylusPresence::NONE};
     // State provided by an external stylus
     StylusState mExternalStylusState;
     // If an external stylus is capable of reporting pointer-specific data like pressure, we will
@@ -372,9 +400,6 @@
     // The time the primary pointer last went down.
     nsecs_t mDownTime{0};
 
-    // The pointer controller, or null if the device is not a pointer.
-    std::shared_ptr<PointerControllerInterface> mPointerController;
-
     std::vector<VirtualKey> mVirtualKeys;
 
     explicit TouchInputMapper(InputDeviceContext& deviceContext,
@@ -445,8 +470,6 @@
     float mTiltYCenter;
     float mTiltYScale;
 
-    bool mExternalStylusConnected;
-
     // Oriented motion ranges for input device info.
     struct OrientedRanges {
         InputDeviceInfo::MotionRange x;
@@ -569,6 +592,8 @@
 
             // Waiting for quiet time to end before starting the next gesture.
             QUIET,
+
+            ftl_last = QUIET,
         };
 
         // When a gesture is sent to an unfocused window, return true if it can bring that window
@@ -688,7 +713,7 @@
 
         // Values reported for the last pointer event.
         uint32_t source;
-        int32_t displayId;
+        ui::LogicalDisplayId displayId{ui::LogicalDisplayId::INVALID};
         float lastCursorX;
         float lastCursorY;
 
@@ -701,7 +726,7 @@
             hovering = false;
             downTime = 0;
             source = 0;
-            displayId = ADISPLAY_ID_NONE;
+            displayId = ui::LogicalDisplayId::INVALID;
             lastCursorX = 0.f;
             lastCursorY = 0.f;
         }
@@ -792,7 +817,8 @@
 
     [[nodiscard]] std::list<NotifyArgs> dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
                                                               uint32_t policyFlags, bool down,
-                                                              bool hovering, int32_t displayId);
+                                                              bool hovering,
+                                                              ui::LogicalDisplayId displayId);
     [[nodiscard]] std::list<NotifyArgs> abortPointerSimple(nsecs_t when, nsecs_t readTime,
                                                            uint32_t policyFlags);
 
@@ -815,9 +841,6 @@
 
     // Returns if this touch device is a touch screen with an associated display.
     bool isTouchScreen();
-    // Updates touch spots if they are enabled. Should only be used when this device is a
-    // touchscreen.
-    void updateTouchSpots();
 
     bool isPointInsidePhysicalFrame(int32_t x, int32_t y) const;
     const VirtualKey* findVirtualKeyHit(int32_t x, int32_t y);
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index eacc66e..9a36bfb 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -24,6 +24,7 @@
 #include <mutex>
 #include <optional>
 
+#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/thread_annotations.h>
 #include <android/input.h>
@@ -35,6 +36,7 @@
 #include <log/log_main.h>
 #include <stats_pull_atom_callback.h>
 #include <statslog.h>
+#include "InputReaderBase.h"
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
 #include "gestures/HardwareProperties.h"
@@ -47,8 +49,6 @@
 
 namespace {
 
-static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
-
 /**
  * Log details of each gesture output by the gestures library.
  * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires
@@ -234,50 +234,39 @@
 
 TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext,
                                          const InputReaderConfiguration& readerConfig)
-      : TouchpadInputMapper(deviceContext, readerConfig, ENABLE_POINTER_CHOREOGRAPHER) {}
-
-TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext,
-                                         const InputReaderConfiguration& readerConfig,
-                                         bool enablePointerChoreographer)
       : InputMapper(deviceContext, readerConfig),
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
-        mPointerController(getContext()->getPointerController(getDeviceId())),
         mTimerProvider(*getContext()),
         mStateConverter(deviceContext, mMotionAccumulator),
         mGestureConverter(*getContext(), deviceContext, getDeviceId()),
         mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
-        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())),
-        mEnablePointerChoreographer(enablePointerChoreographer) {
-    RawAbsoluteAxisInfo slotAxisInfo;
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
-    if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
-        ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
-              "properly.",
-              deviceContext.getName().c_str());
+        mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())) {
+    if (std::optional<RawAbsoluteAxisInfo> slotAxis =
+                deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT);
+        slotAxis && slotAxis->maxValue >= 0) {
+        mMotionAccumulator.configure(deviceContext, slotAxis->maxValue + 1, true);
+    } else {
+        LOG(WARNING) << "Touchpad " << deviceContext.getName()
+                     << " doesn't have a valid ABS_MT_SLOT axis, and probably won't work properly.";
+        mMotionAccumulator.configure(deviceContext, 1, true);
     }
-    mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
 
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
-    mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
+    mHardwareProperties = createHardwareProperties(deviceContext);
+    mGestureInterpreter->SetHardwareProperties(mHardwareProperties);
     // Even though we don't explicitly delete copy/move semantics, it's safe to
     // give away pointers to TouchpadInputMapper and its members here because
     // 1) mGestureInterpreter's lifecycle is determined by TouchpadInputMapper, and
     // 2) TouchpadInputMapper is stored as a unique_ptr and not moved.
     mGestureInterpreter->SetPropProvider(const_cast<GesturesPropProvider*>(&gesturePropProvider),
                                          &mPropertyProvider);
-    if (input_flags::enable_gestures_library_timer_provider()) {
-        mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>(
-                                                      &kGestureTimerProvider),
-                                              &mTimerProvider);
-    }
+    mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>(
+                                                  &kGestureTimerProvider),
+                                          &mTimerProvider);
     mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
 }
 
 TouchpadInputMapper::~TouchpadInputMapper() {
-    if (mPointerController != nullptr) {
-        mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
-    }
-
     // The gesture interpreter's destructor will try to free its property and timer providers,
     // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers.
     // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already
@@ -311,15 +300,12 @@
     dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
     dump += INDENT3 "Gesture properties:\n";
     dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
-    if (input_flags::enable_gestures_library_timer_provider()) {
-        dump += INDENT3 "Timer provider:\n";
-        dump += addLinePrefix(mTimerProvider.dump(), INDENT4);
-    } else {
-        dump += INDENT3 "Timer provider: disabled by flag\n";
-    }
+    dump += INDENT3 "Timer provider:\n";
+    dump += addLinePrefix(mTimerProvider.dump(), INDENT4);
     dump += INDENT3 "Captured event converter:\n";
     dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
-    dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
+    dump += StringPrintf(INDENT3 "DisplayId: %s\n",
+                         toString(mDisplayId, streamableToString).c_str());
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
@@ -331,7 +317,7 @@
     }
 
     if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
-        mDisplayId = ADISPLAY_ID_NONE;
+        mDisplayId = ui::LogicalDisplayId::INVALID;
         std::optional<DisplayViewport> resolvedViewport;
         std::optional<FloatRect> boundsInLogicalDisplay;
         if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
@@ -339,33 +325,13 @@
             // Only generate events for the associated display.
             mDisplayId = assocViewport->displayId;
             resolvedViewport = *assocViewport;
-            if (!mEnablePointerChoreographer) {
-                const bool mismatchedPointerDisplay =
-                        (assocViewport->displayId != mPointerController->getDisplayId());
-                if (mismatchedPointerDisplay) {
-                    ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
-                          "controller",
-                          mDeviceContext.getName().c_str());
-                    mDisplayId.reset();
-                }
-            }
         } else {
             // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
-            if (mEnablePointerChoreographer) {
-                // Always use DISPLAY_ID_NONE for touchpad events.
-                // PointerChoreographer will make it target the correct the displayId later.
-                resolvedViewport =
-                        getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
-                mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
-            } else {
-                mDisplayId = mPointerController->getDisplayId();
-                if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
-                    resolvedViewport = *v;
-                }
-                if (auto bounds = mPointerController->getBounds(); bounds) {
-                    boundsInLogicalDisplay = *bounds;
-                }
-            }
+            // Always use DISPLAY_ID_NONE for touchpad events.
+            // PointerChoreographer will make it target the correct the displayId later.
+            resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+            mDisplayId = resolvedViewport ? std::make_optional(ui::LogicalDisplayId::INVALID)
+                                          : std::nullopt;
         }
 
         mGestureConverter.setDisplayId(mDisplayId);
@@ -408,18 +374,18 @@
                 .setBoolValues({config.touchpadTapDraggingEnabled});
         mPropertyProvider.getProperty("Button Right Click Zone Enable")
                 .setBoolValues({config.touchpadRightClickZoneEnabled});
+        mTouchpadHardwareStateNotificationsEnabled = config.shouldNotifyTouchpadHardwareState;
     }
     std::list<NotifyArgs> out;
-    if ((!changes.any() && config.pointerCaptureRequest.enable) ||
+    if ((!changes.any() && config.pointerCaptureRequest.isEnable()) ||
         changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
-        mPointerCaptured = config.pointerCaptureRequest.enable;
+        mPointerCaptured = config.pointerCaptureRequest.isEnable();
         // The motion ranges are going to change, so bump the generation to clear the cached ones.
         bumpGeneration();
         if (mPointerCaptured) {
             // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are
             // still being reported for a gesture in progress.
             out += reset(when);
-            mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
         } else {
             // We're transitioning from captured to uncaptured.
             mCapturedEventConverter.reset();
@@ -449,17 +415,20 @@
     mResettingInterpreter = false;
 }
 
-std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent& rawEvent) {
     if (mPointerCaptured) {
-        return mCapturedEventConverter.process(*rawEvent);
+        return mCapturedEventConverter.process(rawEvent);
     }
     if (mMotionAccumulator.getActiveSlotsCount() == 0) {
-        mGestureStartTime = rawEvent->when;
+        mGestureStartTime = rawEvent.when;
     }
     std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
     if (state) {
+        if (mTouchpadHardwareStateNotificationsEnabled) {
+            getPolicy()->notifyTouchpadHardwareState(*state, getDeviceId());
+        }
         updatePalmDetectionMetrics();
-        return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
+        return sendHardwareState(rawEvent.when, rawEvent.readTime, *state);
     } else {
         return {};
     }
@@ -499,9 +468,6 @@
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::timeoutExpired(nsecs_t when) {
-    if (!input_flags::enable_gestures_library_timer_provider()) {
-        return {};
-    }
     mTimerProvider.triggerCallbacks(when);
     return processGestures(when, when);
 }
@@ -514,6 +480,9 @@
         return;
     }
     mGesturesToProcess.push_back(*gesture);
+    if (mTouchpadHardwareStateNotificationsEnabled) {
+        getPolicy()->notifyTouchpadGestureInfo(gesture->type, getDeviceId());
+    }
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
@@ -529,8 +498,12 @@
     return out;
 }
 
-std::optional<int32_t> TouchpadInputMapper::getAssociatedDisplayId() {
+std::optional<ui::LogicalDisplayId> TouchpadInputMapper::getAssociatedDisplayId() {
     return mDisplayId;
 }
 
+std::optional<HardwareProperties> TouchpadInputMapper::getTouchpadHardwareProperties() {
+    return mHardwareProperties;
+}
+
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 9f272cf..a2c4be9 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -56,7 +56,7 @@
                                                     const InputReaderConfiguration& config,
                                                     ConfigurationChanges changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
     [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
 
     void consumeGesture(const Gesture* gesture);
@@ -66,16 +66,14 @@
     using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
                                          uint16_t /*productId*/, uint16_t /*version*/>;
 
-    std::optional<int32_t> getAssociatedDisplayId() override;
+    std::optional<ui::LogicalDisplayId> getAssociatedDisplayId() override;
+
+    std::optional<HardwareProperties> getTouchpadHardwareProperties() override;
 
 private:
     void resetGestureInterpreter(nsecs_t when);
     explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
                                  const InputReaderConfiguration& readerConfig);
-    // Constructor for testing.
-    explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
-                                 const InputReaderConfiguration& readerConfig,
-                                 bool enablePointerChoreographer);
     void updatePalmDetectionMetrics();
     [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
                                                           SelfContainedHardwareState schs);
@@ -83,7 +81,6 @@
 
     std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
             mGestureInterpreter;
-    std::shared_ptr<PointerControllerInterface> mPointerController;
 
     PropertyProvider mPropertyProvider;
     TimerProvider mTimerProvider;
@@ -97,6 +94,7 @@
     HardwareStateConverter mStateConverter;
     GestureConverter mGestureConverter;
     CapturedTouchpadEventConverter mCapturedEventConverter;
+    HardwareProperties mHardwareProperties;
 
     bool mPointerCaptured = false;
     bool mResettingInterpreter = false;
@@ -111,14 +109,16 @@
     // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
     std::set<int32_t> mPalmTrackingIds;
 
-    const bool mEnablePointerChoreographer;
-
     // The display that events generated by this mapper should target. This can be set to
-    // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+    // LogicalDisplayId::INVALID to target the focused display. If there is no display target (i.e.
     // std::nullopt), all events will be ignored.
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
 
     nsecs_t mGestureStartTime{0};
+
+    // True if hardware state update notifications is available for usage based on its feature flag
+    // and settings value.
+    bool mTouchpadHardwareStateNotificationsEnabled = false;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 8d78d0f..a3a48ef 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
@@ -36,7 +36,7 @@
     info.setVibrator(true);
 }
 
-std::list<NotifyArgs> VibratorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> VibratorInputMapper::process(const RawEvent& rawEvent) {
     // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
     return {};
 }
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.h b/services/inputflinger/reader/mapper/VibratorInputMapper.h
index 9079c73..7519682 100644
--- a/services/inputflinger/reader/mapper/VibratorInputMapper.h
+++ b/services/inputflinger/reader/mapper/VibratorInputMapper.h
@@ -30,7 +30,7 @@
 
     virtual uint32_t getSources() const override;
     virtual void populateDeviceInfo(InputDeviceInfo& deviceInfo) override;
-    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+    [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent) override;
 
     [[nodiscard]] std::list<NotifyArgs> vibrate(const VibrationSequence& sequence, ssize_t repeat,
                                                 int32_t token) override;
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
index 153236c..9e722d4 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp
@@ -47,32 +47,32 @@
     mBtnTask = 0;
 }
 
-void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->code) {
+void CursorButtonAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_KEY) {
+        switch (rawEvent.code) {
             case BTN_LEFT:
-                mBtnLeft = rawEvent->value;
+                mBtnLeft = rawEvent.value;
                 break;
             case BTN_RIGHT:
-                mBtnRight = rawEvent->value;
+                mBtnRight = rawEvent.value;
                 break;
             case BTN_MIDDLE:
-                mBtnMiddle = rawEvent->value;
+                mBtnMiddle = rawEvent.value;
                 break;
             case BTN_BACK:
-                mBtnBack = rawEvent->value;
+                mBtnBack = rawEvent.value;
                 break;
             case BTN_SIDE:
-                mBtnSide = rawEvent->value;
+                mBtnSide = rawEvent.value;
                 break;
             case BTN_FORWARD:
-                mBtnForward = rawEvent->value;
+                mBtnForward = rawEvent.value;
                 break;
             case BTN_EXTRA:
-                mBtnExtra = rawEvent->value;
+                mBtnExtra = rawEvent.value;
                 break;
             case BTN_TASK:
-                mBtnTask = rawEvent->value;
+                mBtnTask = rawEvent.value;
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
index 6960644..256b2bb 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h
@@ -29,7 +29,7 @@
     CursorButtonAccumulator();
     void reset(const InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
 
     uint32_t getButtonState() const;
     inline bool isLeftPressed() const { return mBtnLeft; }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
index 0714694..5373440 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.cpp
@@ -16,18 +16,29 @@
 
 #include "CursorScrollAccumulator.h"
 
+#include <android_companion_virtualdevice_flags.h>
 #include "EventHub.h"
 #include "InputDevice.h"
 
 namespace android {
 
-CursorScrollAccumulator::CursorScrollAccumulator() : mHaveRelWheel(false), mHaveRelHWheel(false) {
+namespace vd_flags = android::companion::virtualdevice::flags;
+
+CursorScrollAccumulator::CursorScrollAccumulator()
+      : mHaveRelWheel(false),
+        mHaveRelHWheel(false),
+        mHaveRelWheelHighRes(false),
+        mHaveRelHWheelHighRes(false) {
     clearRelativeAxes();
 }
 
 void CursorScrollAccumulator::configure(InputDeviceContext& deviceContext) {
     mHaveRelWheel = deviceContext.hasRelativeAxis(REL_WHEEL);
     mHaveRelHWheel = deviceContext.hasRelativeAxis(REL_HWHEEL);
+    if (vd_flags::high_resolution_scroll()) {
+        mHaveRelWheelHighRes = deviceContext.hasRelativeAxis(REL_WHEEL_HI_RES);
+        mHaveRelHWheelHighRes = deviceContext.hasRelativeAxis(REL_HWHEEL_HI_RES);
+    }
 }
 
 void CursorScrollAccumulator::reset(InputDeviceContext& deviceContext) {
@@ -39,14 +50,34 @@
     mRelHWheel = 0;
 }
 
-void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_REL) {
-        switch (rawEvent->code) {
+void CursorScrollAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_REL) {
+        switch (rawEvent.code) {
+            case REL_WHEEL_HI_RES:
+                if (mHaveRelWheelHighRes) {
+                    mRelWheel =
+                            rawEvent.value / static_cast<float>(kEvdevHighResScrollUnitsPerDetent);
+                }
+                break;
+            case REL_HWHEEL_HI_RES:
+                if (mHaveRelHWheelHighRes) {
+                    mRelHWheel =
+                            rawEvent.value / static_cast<float>(kEvdevHighResScrollUnitsPerDetent);
+                }
+                break;
             case REL_WHEEL:
-                mRelWheel = rawEvent->value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelWheelHighRes) {
+                    mRelWheel = rawEvent.value;
+                }
                 break;
             case REL_HWHEEL:
-                mRelHWheel = rawEvent->value;
+                // We should ignore regular scroll events, if we have already have high-res scroll
+                // enabled.
+                if (!mHaveRelHWheelHighRes) {
+                    mRelHWheel = rawEvent.value;
+                }
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
index ae1b7a3..d3373cc 100644
--- a/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h
@@ -16,40 +16,35 @@
 
 #pragma once
 
-#include <stdint.h>
-
 namespace android {
 
 class InputDeviceContext;
 struct RawEvent;
 
 /* Keeps track of cursor scrolling motions. */
-
 class CursorScrollAccumulator {
 public:
     CursorScrollAccumulator();
     void configure(InputDeviceContext& deviceContext);
     void reset(InputDeviceContext& deviceContext);
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     inline bool haveRelativeVWheel() const { return mHaveRelWheel; }
     inline bool haveRelativeHWheel() const { return mHaveRelHWheel; }
 
-    inline int32_t getRelativeX() const { return mRelX; }
-    inline int32_t getRelativeY() const { return mRelY; }
-    inline int32_t getRelativeVWheel() const { return mRelWheel; }
-    inline int32_t getRelativeHWheel() const { return mRelHWheel; }
+    inline float getRelativeVWheel() const { return mRelWheel; }
+    inline float getRelativeHWheel() const { return mRelHWheel; }
 
 private:
     bool mHaveRelWheel;
     bool mHaveRelHWheel;
+    bool mHaveRelWheelHighRes;
+    bool mHaveRelHWheelHighRes;
 
-    int32_t mRelX;
-    int32_t mRelY;
-    int32_t mRelWheel;
-    int32_t mRelHWheel;
+    float mRelWheel;
+    float mRelHWheel;
 
     void clearRelativeAxes();
 };
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
index b3f1700..8dc6e4d 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -45,12 +45,12 @@
     mCurrentSlot = -1;
 }
 
-void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_ABS) {
+void MultiTouchMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_ABS) {
         bool newSlot = false;
         if (mUsingSlotsProtocol) {
-            if (rawEvent->code == ABS_MT_SLOT) {
-                mCurrentSlot = rawEvent->value;
+            if (rawEvent.code == ABS_MT_SLOT) {
+                mCurrentSlot = rawEvent.value;
                 newSlot = true;
             }
         } else if (mCurrentSlot < 0) {
@@ -72,12 +72,12 @@
             if (!mUsingSlotsProtocol) {
                 slot.mInUse = true;
             }
-            if (rawEvent->code == ABS_MT_POSITION_X || rawEvent->code == ABS_MT_POSITION_Y) {
-                warnIfNotInUse(*rawEvent, slot);
+            if (rawEvent.code == ABS_MT_POSITION_X || rawEvent.code == ABS_MT_POSITION_Y) {
+                warnIfNotInUse(rawEvent, slot);
             }
-            slot.populateAxisValue(rawEvent->code, rawEvent->value);
+            slot.populateAxisValue(rawEvent.code, rawEvent.value);
         }
-    } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
+    } else if (rawEvent.type == EV_SYN && rawEvent.code == SYN_MT_REPORT) {
         // MultiTouch Sync: The driver has returned all data for *one* of the pointers.
         mCurrentSlot += 1;
     }
@@ -139,13 +139,11 @@
     if (!mUsingSlotsProtocol) {
         return;
     }
-    int32_t initialSlot;
-    if (const auto status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
-        status == OK) {
-        mCurrentSlot = initialSlot;
+    if (const std::optional<int32_t> initialSlot = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT);
+        initialSlot.has_value()) {
+        mCurrentSlot = initialSlot.value();
     } else {
-        ALOGE("Could not retrieve current multi-touch slot index. status=%s",
-              statusToString(status).c_str());
+        ALOGE("Could not retrieve current multi-touch slot index");
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
index a0f2147..388ed82 100644
--- a/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -76,7 +76,7 @@
     void configure(const InputDeviceContext& deviceContext, size_t slotCount,
                    bool usingSlotsProtocol);
     void reset(const InputDeviceContext& deviceContext);
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void finishSync();
 
     size_t getActiveSlotsCount() const;
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
index 27b8e40..4cf9243 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.cpp
@@ -26,13 +26,13 @@
 }
 
 void SingleTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
-    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X);
-    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y);
-    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE);
-    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH);
-    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE);
-    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X);
-    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y);
+    mAbsX = deviceContext.getAbsoluteAxisValue(ABS_X).value_or(0);
+    mAbsY = deviceContext.getAbsoluteAxisValue(ABS_Y).value_or(0);
+    mAbsPressure = deviceContext.getAbsoluteAxisValue(ABS_PRESSURE).value_or(0);
+    mAbsToolWidth = deviceContext.getAbsoluteAxisValue(ABS_TOOL_WIDTH).value_or(0);
+    mAbsDistance = deviceContext.getAbsoluteAxisValue(ABS_DISTANCE).value_or(0);
+    mAbsTiltX = deviceContext.getAbsoluteAxisValue(ABS_TILT_X).value_or(0);
+    mAbsTiltY = deviceContext.getAbsoluteAxisValue(ABS_TILT_Y).value_or(0);
 }
 
 void SingleTouchMotionAccumulator::clearAbsoluteAxes() {
@@ -45,29 +45,29 @@
     mAbsTiltY = 0;
 }
 
-void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
-    if (rawEvent->type == EV_ABS) {
-        switch (rawEvent->code) {
+void SingleTouchMotionAccumulator::process(const RawEvent& rawEvent) {
+    if (rawEvent.type == EV_ABS) {
+        switch (rawEvent.code) {
             case ABS_X:
-                mAbsX = rawEvent->value;
+                mAbsX = rawEvent.value;
                 break;
             case ABS_Y:
-                mAbsY = rawEvent->value;
+                mAbsY = rawEvent.value;
                 break;
             case ABS_PRESSURE:
-                mAbsPressure = rawEvent->value;
+                mAbsPressure = rawEvent.value;
                 break;
             case ABS_TOOL_WIDTH:
-                mAbsToolWidth = rawEvent->value;
+                mAbsToolWidth = rawEvent.value;
                 break;
             case ABS_DISTANCE:
-                mAbsDistance = rawEvent->value;
+                mAbsDistance = rawEvent.value;
                 break;
             case ABS_TILT_X:
-                mAbsTiltX = rawEvent->value;
+                mAbsTiltX = rawEvent.value;
                 break;
             case ABS_TILT_Y:
-                mAbsTiltY = rawEvent->value;
+                mAbsTiltY = rawEvent.value;
                 break;
         }
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
index 93056f0..fb74bca 100644
--- a/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h
@@ -28,7 +28,7 @@
 public:
     SingleTouchMotionAccumulator();
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
     void reset(InputDeviceContext& deviceContext);
 
     inline int32_t getAbsoluteX() const { return mAbsX; }
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
index 8c4bed3..ba8577e 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp
@@ -52,60 +52,60 @@
     mHidUsageAccumulator.reset();
 }
 
-void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
-    mHidUsageAccumulator.process(*rawEvent);
+void TouchButtonAccumulator::process(const RawEvent& rawEvent) {
+    mHidUsageAccumulator.process(rawEvent);
 
-    if (rawEvent->type == EV_KEY) {
-        switch (rawEvent->code) {
+    if (rawEvent.type == EV_KEY) {
+        switch (rawEvent.code) {
             case BTN_TOUCH:
-                mBtnTouch = rawEvent->value;
+                mBtnTouch = rawEvent.value;
                 break;
             case BTN_STYLUS:
-                mBtnStylus = rawEvent->value;
+                mBtnStylus = rawEvent.value;
                 break;
             case BTN_STYLUS2:
             case BTN_0: // BTN_0 is what gets mapped for the HID usage
                         // Digitizers.SecondaryBarrelSwitch
-                mBtnStylus2 = rawEvent->value;
+                mBtnStylus2 = rawEvent.value;
                 break;
             case BTN_TOOL_FINGER:
-                mBtnToolFinger = rawEvent->value;
+                mBtnToolFinger = rawEvent.value;
                 break;
             case BTN_TOOL_PEN:
-                mBtnToolPen = rawEvent->value;
+                mBtnToolPen = rawEvent.value;
                 break;
             case BTN_TOOL_RUBBER:
-                mBtnToolRubber = rawEvent->value;
+                mBtnToolRubber = rawEvent.value;
                 break;
             case BTN_TOOL_BRUSH:
-                mBtnToolBrush = rawEvent->value;
+                mBtnToolBrush = rawEvent.value;
                 break;
             case BTN_TOOL_PENCIL:
-                mBtnToolPencil = rawEvent->value;
+                mBtnToolPencil = rawEvent.value;
                 break;
             case BTN_TOOL_AIRBRUSH:
-                mBtnToolAirbrush = rawEvent->value;
+                mBtnToolAirbrush = rawEvent.value;
                 break;
             case BTN_TOOL_MOUSE:
-                mBtnToolMouse = rawEvent->value;
+                mBtnToolMouse = rawEvent.value;
                 break;
             case BTN_TOOL_LENS:
-                mBtnToolLens = rawEvent->value;
+                mBtnToolLens = rawEvent.value;
                 break;
             case BTN_TOOL_DOUBLETAP:
-                mBtnToolDoubleTap = rawEvent->value;
+                mBtnToolDoubleTap = rawEvent.value;
                 break;
             case BTN_TOOL_TRIPLETAP:
-                mBtnToolTripleTap = rawEvent->value;
+                mBtnToolTripleTap = rawEvent.value;
                 break;
             case BTN_TOOL_QUADTAP:
-                mBtnToolQuadTap = rawEvent->value;
+                mBtnToolQuadTap = rawEvent.value;
                 break;
             case BTN_TOOL_QUINTTAP:
-                mBtnToolQuintTap = rawEvent->value;
+                mBtnToolQuintTap = rawEvent.value;
                 break;
             default:
-                processMappedKey(rawEvent->code, rawEvent->value);
+                processMappedKey(rawEvent.code, rawEvent.value);
         }
         return;
     }
diff --git a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
index e829692..c7adf84 100644
--- a/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
+++ b/services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h
@@ -33,7 +33,7 @@
     void configure();
     void reset();
 
-    void process(const RawEvent* rawEvent);
+    void process(const RawEvent& rawEvent);
 
     uint32_t getButtonState() const;
     ToolType getToolType() const;
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 764bb56..da2c683 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -60,17 +60,33 @@
     }
 }
 
+bool isGestureNoFocusChange(MotionClassification classification) {
+    switch (classification) {
+        case MotionClassification::TWO_FINGER_SWIPE:
+        case MotionClassification::MULTI_FINGER_SWIPE:
+        case MotionClassification::PINCH:
+            // Most gestures can be performed on an unfocused window, so they should not
+            // not affect window focus.
+            return true;
+        case MotionClassification::NONE:
+        case MotionClassification::AMBIGUOUS_GESTURE:
+        case MotionClassification::DEEP_PRESS:
+            return false;
+    }
+}
+
 } // namespace
 
 GestureConverter::GestureConverter(InputReaderContext& readerContext,
                                    const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mPointerController(readerContext.getPointerController(deviceId)),
-        mEnableFlingStop(input_flags::enable_touchpad_fling_stop()) {
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
-    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
-}
+        mEnableFlingStop(input_flags::enable_touchpad_fling_stop()),
+        mEnableNoFocusChange(input_flags::enable_touchpad_no_focus_change()),
+        // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub
+        // won't classify a device as a touchpad if they're not present.
+        mXAxisInfo(deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X).value()),
+        mYAxisInfo(deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y).value()) {}
 
 std::string GestureConverter::dump() const {
     std::stringstream out;
@@ -174,7 +190,6 @@
                                                    const Gesture& gesture) {
     float deltaX = gesture.details.move.dx;
     float deltaY = gesture.details.move.dy;
-    const auto [oldXCursorPosition, oldYCursorPosition] = mPointerController->getPosition();
     if (ENABLE_TOUCHPAD_PALM_REJECTION_V2) {
         bool wasHoverCancelled = mIsHoverCancelled;
         // Gesture will be cancelled if it started before the user started typing and
@@ -185,8 +200,7 @@
         if (!wasHoverCancelled && mIsHoverCancelled) {
             // This is the first event of the cancelled gesture, we won't return because we need to
             // generate a HOVER_EXIT event
-            mPointerController->fade(PointerControllerInterface::Transition::GRADUAL);
-            return exitHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
+            return exitHover(when, readTime);
         } else if (mIsHoverCancelled) {
             return {};
         }
@@ -202,30 +216,23 @@
             (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
             enableTapToClick(when);
         }
-        mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-        mPointerController->move(deltaX, deltaY);
-        mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
 
     std::list<NotifyArgs> out;
     const bool down = isPointerDown(mButtonState);
     if (!down) {
-        out += enterHover(when, readTime, oldXCursorPosition, oldYCursorPosition);
+        out += enterHover(when, readTime);
     }
-    const auto [newXCursorPosition, newYCursorPosition] = mPointerController->getPosition();
 
     PointerCoords coords;
     coords.clear();
-    coords.setAxisValue(AMOTION_EVENT_AXIS_X, newXCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, newYCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
     coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
     const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
     out.push_back(makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
-                                 /*pointerCount=*/1, &coords, newXCursorPosition,
-                                 newYCursorPosition));
+                                 /*pointerCount=*/1, &coords));
     return out;
 }
 
@@ -233,15 +240,8 @@
                                                             const Gesture& gesture) {
     std::list<NotifyArgs> out = {};
 
-    mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
-
-    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);
 
@@ -274,16 +274,15 @@
             newButtonState |= actionButton;
             pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                  actionButton, newButtonState,
-                                                 /*pointerCount=*/1, &coords, xCursorPosition,
-                                                 yCursorPosition));
+                                                 /*pointerCount=*/1, &coords));
         }
     }
     if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
         mDownTime = when;
-        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+        out += exitHover(when, readTime);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     &coords, xCursorPosition, yCursorPosition));
+                                     &coords));
     }
     out.splice(out.end(), pressEvents);
 
@@ -299,16 +298,15 @@
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
-                                         &coords, xCursorPosition, yCursorPosition));
+                                         &coords));
         }
     }
     if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, &coords,
-                                     xCursorPosition, yCursorPosition));
+                                     newButtonState, /* pointerCount= */ 1, &coords));
         mButtonState = newButtonState;
-        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+        out += enterHover(when, readTime);
     }
     mButtonState = newButtonState;
     return out;
@@ -316,12 +314,9 @@
 
 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);
@@ -332,17 +327,15 @@
         if (mButtonState & button) {
             newButtonState &= ~button;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
-                                         button, newButtonState, /*pointerCount=*/1, &coords,
-                                         xCursorPosition, yCursorPosition));
+                                         button, newButtonState, /*pointerCount=*/1, &coords));
         }
     }
     mButtonState = 0;
     if (pointerDown) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                     mButtonState, /*pointerCount=*/1, &coords, xCursorPosition,
-                                     yCursorPosition));
-        out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+                                     mButtonState, /*pointerCount=*/1, &coords));
+        out += enterHover(when, readTime);
     }
     return out;
 }
@@ -351,20 +344,16 @@
                                                      const Gesture& gesture) {
     std::list<NotifyArgs> out;
     PointerCoords& coords = mFakeFingerCoords[0];
-    const auto [xCursorPosition, yCursorPosition] = mPointerController->getPosition();
     if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
-        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+        out += exitHover(when, readTime);
 
         mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
-        coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
-        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        coords.clear();
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
-                               mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
-                               xCursorPosition, yCursorPosition);
-        args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+                               mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
         out.push_back(args);
     }
     float deltaX = gesture.details.scroll.dx;
@@ -378,9 +367,7 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, -gesture.details.scroll.dy);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
-                           xCursorPosition, yCursorPosition);
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
     out.push_back(args);
     return out;
 }
@@ -409,28 +396,23 @@
                     // avoid side effects (e.g. activation of UI elements).
                     // TODO(b/326056750): add an API for fling stops.
                     mFlingMayBeInProgress = false;
-                    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);
 
                     std::list<NotifyArgs> out;
                     mDownTime = when;
-                    out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
-                    // TODO(b/281106755): add a MotionClassification value for fling stops.
+                    mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
+                    out += exitHover(when, readTime);
                     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                                  /*actionButton=*/0, /*buttonState=*/0,
-                                                 /*pointerCount=*/1, &coords, xCursorPosition,
-                                                 yCursorPosition));
+                                                 /*pointerCount=*/1, &coords));
                     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_CANCEL,
                                                  /*actionButton=*/0, /*buttonState=*/0,
-                                                 /*pointerCount=*/1, &coords, xCursorPosition,
-                                                 yCursorPosition));
-                    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+                                                 /*pointerCount=*/1, &coords));
+                    out += enterHover(when, readTime);
+                    mCurrentClassification = MotionClassification::NONE;
                     return out;
                 } else {
                     // Use the tap down state of a fling gesture as an indicator that a contact
@@ -454,17 +436,14 @@
 
 std::list<NotifyArgs> GestureConverter::endScroll(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
-    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);
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data(),
-                           xCursorPosition, yCursorPosition);
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+                           mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
     out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
-    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+    out += enterHover(when, readTime);
     return out;
 }
 
@@ -474,26 +453,26 @@
                                                                              float dx, float dy) {
     std::list<NotifyArgs> out = {};
 
-    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
         // separate swipes with an appropriate lift event between them, so we don't have to worry
         // about the finger count changing mid-swipe.
 
-        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+        out += exitHover(when, readTime);
 
         mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
 
         mSwipeFingerCount = fingerCount;
 
         constexpr float FAKE_FINGER_SPACING = 100;
-        float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
+        float xCoord = 0.f - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
         for (size_t i = 0; i < mSwipeFingerCount; i++) {
             PointerCoords& coords = mFakeFingerCoords[i];
             coords.clear();
+            // PointerChoreographer will add the cursor position to these pointers.
             coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord);
-            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
             coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
             xCoord += FAKE_FINGER_SPACING;
         }
@@ -503,14 +482,13 @@
                                           fingerCount);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                     mFakeFingerCoords.data()));
         for (size_t i = 1; i < mSwipeFingerCount; i++) {
             out.push_back(makeMotionArgs(when, readTime,
                                          AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                  (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                          /* actionButton= */ 0, mButtonState,
-                                         /* pointerCount= */ i + 1, mFakeFingerCoords.data(),
-                                         xCursorPosition, yCursorPosition));
+                                         /* pointerCount= */ i + 1, mFakeFingerCoords.data()));
         }
     }
     float rotatedDeltaX = dx, rotatedDeltaY = -dy;
@@ -528,7 +506,7 @@
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                                  mButtonState, /* pointerCount= */ mSwipeFingerCount,
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                 mFakeFingerCoords.data()));
     return out;
 }
 
@@ -538,7 +516,6 @@
     if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
         return out;
     }
-    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);
 
@@ -547,22 +524,20 @@
                                      AMOTION_EVENT_ACTION_POINTER_UP |
                                              ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
-                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                     mFakeFingerCoords.data()));
     }
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
                                  /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                 mFakeFingerCoords.data()));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0);
     mCurrentClassification = MotionClassification::NONE;
-    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+    out += enterHover(when, readTime);
     mSwipeFingerCount = 0;
     return out;
 }
 
 [[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
                                                                   const Gesture& gesture) {
-    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
     // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in
@@ -574,28 +549,27 @@
                             gesture.details.pinch.zoom_state);
         std::list<NotifyArgs> out;
 
-        out += exitHover(when, readTime, xCursorPosition, yCursorPosition);
+        out += exitHover(when, readTime);
 
         mCurrentClassification = MotionClassification::PINCH;
         mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
+        // PointerChoreographer will add the cursor position to these pointers.
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                          xCursorPosition - mPinchFingerSeparation / 2);
-        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
         mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
-        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                          xCursorPosition + mPinchFingerSeparation / 2);
-        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
         mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
         mDownTime = when;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
-                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                     mFakeFingerCoords.data()));
         out.push_back(makeMotionArgs(when, readTime,
                                      AMOTION_EVENT_ACTION_POINTER_DOWN |
                                              1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
                                      /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
-                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                     mFakeFingerCoords.data()));
         return out;
     }
 
@@ -604,77 +578,77 @@
     }
 
     mPinchFingerSeparation *= gesture.details.pinch.dz;
+    // PointerChoreographer will add the cursor position to these pointers.
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
                                       gesture.details.pinch.dz);
-    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      xCursorPosition - mPinchFingerSeparation / 2);
-    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
-                                      xCursorPosition + mPinchFingerSeparation / 2);
-    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f - mPinchFingerSeparation / 2);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 0.f + mPinchFingerSeparation / 2);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 0.f);
     return {makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /*actionButton=*/0,
-                           mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data(),
-                           xCursorPosition, yCursorPosition)};
+                           mButtonState, /*pointerCount=*/2, mFakeFingerCoords.data())};
 }
 
 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,
-                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+                                 mFakeFingerCoords.data()));
     out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /*actionButton=*/0,
-                                 mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data(),
-                                 xCursorPosition, yCursorPosition));
+                                 mButtonState, /*pointerCount=*/1, mFakeFingerCoords.data()));
     mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
     mCurrentClassification = MotionClassification::NONE;
-    out += enterHover(when, readTime, xCursorPosition, yCursorPosition);
+    out += enterHover(when, readTime);
     return out;
 }
 
-std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime,
-                                                   float xCursorPosition, float yCursorPosition) {
+std::list<NotifyArgs> GestureConverter::enterHover(nsecs_t when, nsecs_t readTime) {
     if (!mIsHovering) {
         mIsHovering = true;
-        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER, xCursorPosition,
-                               yCursorPosition)};
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_ENTER)};
     } else {
         return {};
     }
 }
 
-std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime,
-                                                  float xCursorPosition, float yCursorPosition) {
+std::list<NotifyArgs> GestureConverter::exitHover(nsecs_t when, nsecs_t readTime) {
     if (mIsHovering) {
         mIsHovering = false;
-        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT, xCursorPosition,
-                               yCursorPosition)};
+        return {makeHoverEvent(when, readTime, AMOTION_EVENT_ACTION_HOVER_EXIT)};
     } else {
         return {};
     }
 }
 
-NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
-                                                  float xCursorPosition, float yCursorPosition) {
+NotifyMotionArgs GestureConverter::makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action) {
     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);
     return makeMotionArgs(when, readTime, action, /*actionButton=*/0, mButtonState,
-                          /*pointerCount=*/1, &coords, xCursorPosition, yCursorPosition);
+                          /*pointerCount=*/1, &coords);
 }
 
 NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
-                                                  const PointerCoords* pointerCoords,
-                                                  float xCursorPosition, float yCursorPosition) {
+                                                  const PointerCoords* pointerCoords) {
+    int32_t flags = 0;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    if (mEnableNoFocusChange && isGestureNoFocusChange(mCurrentClassification)) {
+        flags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+    }
+    if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) {
+        // This helps to make GestureDetector responsive.
+        flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    }
+
     return {mReaderContext.getNextId(),
             when,
             readTime,
@@ -684,7 +658,7 @@
             /* policyFlags= */ POLICY_FLAG_WAKE,
             action,
             /* actionButton= */ actionButton,
-            /* flags= */ action == AMOTION_EVENT_ACTION_CANCEL ? AMOTION_EVENT_FLAG_CANCELED : 0,
+            flags,
             mReaderContext.getGlobalMetaState(),
             buttonState,
             mCurrentClassification,
@@ -694,8 +668,8 @@
             pointerCoords,
             /* xPrecision= */ 1.0f,
             /* yPrecision= */ 1.0f,
-            xCursorPosition,
-            yCursorPosition,
+            /* xCursorPosition= */ 0.f,
+            /* yCursorPosition= */ 0.f,
             /* downTime= */ mDownTime,
             /* videoFrames= */ {}};
 }
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index c8f437e..c9a35c1 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -20,7 +20,6 @@
 #include <list>
 #include <memory>
 
-#include <PointerControllerInterface.h>
 #include <android/input.h>
 #include <utils/Timers.h>
 
@@ -41,8 +40,7 @@
  */
 constexpr std::chrono::nanoseconds TAP_ENABLE_DELAY_NANOS = 400ms;
 
-// Converts Gesture structs from the gestures library into NotifyArgs and the appropriate
-// PointerController calls.
+// Converts Gesture structs from the gestures library into NotifyArgs.
 class GestureConverter {
 public:
     GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
@@ -53,7 +51,7 @@
     void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
 
-    void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
+    void setDisplayId(std::optional<ui::LogicalDisplayId> displayId) { mDisplayId = displayId; }
 
     void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; }
 
@@ -85,18 +83,14 @@
                                                     const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> endPinch(nsecs_t when, nsecs_t readTime);
 
-    [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime,
-                                                   float xCursorPosition, float yCursorPosition);
-    [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime,
-                                                  float xCursorPosition, float yCursorPosition);
+    [[nodiscard]] std::list<NotifyArgs> enterHover(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> exitHover(nsecs_t when, nsecs_t readTime);
 
-    NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action,
-                                    float xCursorPosition, float yCursorPosition);
+    NotifyMotionArgs makeHoverEvent(nsecs_t when, nsecs_t readTime, int32_t action);
 
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
-                                    uint32_t pointerCount, const PointerCoords* pointerCoords,
-                                    float xCursorPosition, float yCursorPosition);
+                                    uint32_t pointerCount, const PointerCoords* pointerCoords);
 
     void enableTapToClick(nsecs_t when);
     bool mIsHoverCancelled{false};
@@ -104,10 +98,10 @@
 
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
-    std::shared_ptr<PointerControllerInterface> mPointerController;
     const bool mEnableFlingStop;
+    const bool mEnableNoFocusChange;
 
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     FloatRect mBoundsInLogicalDisplay{};
     ui::Rotation mOrientation = ui::ROTATION_0;
     RawAbsoluteAxisInfo mXAxisInfo;
diff --git a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
index 81b4968..26028c5 100644
--- a/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp
@@ -26,15 +26,30 @@
 
 extern "C" {
 
+namespace {
+
+/**
+ * Log details of each gesture output by the gestures library.
+ * Enable this via "adb shell setprop log.tag.TouchpadInputMapperGestures DEBUG" (requires
+ * restarting the shell)
+ */
+const bool DEBUG_TOUCHPAD_GESTURES =
+        __android_log_is_loggable(ANDROID_LOG_DEBUG, "TouchpadInputMapperGestures",
+                                  ANDROID_LOG_INFO);
+
+} // namespace
+
 void gestures_log(int verb, const char* fmt, ...) {
     va_list args;
     va_start(args, fmt);
     if (verb == GESTURES_LOG_ERROR) {
         LOG_PRI_VA(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
-    } else if (verb == GESTURES_LOG_INFO) {
-        LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
-    } else {
-        LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+    } else if (DEBUG_TOUCHPAD_GESTURES) {
+        if (verb == GESTURES_LOG_INFO) {
+            LOG_PRI_VA(ANDROID_LOG_INFO, LOG_TAG, fmt, args);
+        } else {
+            LOG_PRI_VA(ANDROID_LOG_DEBUG, LOG_TAG, fmt, args);
+        }
     }
     va_end(args);
 }
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
index 04655dc..d8a1f50 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
@@ -16,6 +16,8 @@
 
 #include "HardwareProperties.h"
 
+#include <optional>
+
 namespace android {
 
 namespace {
@@ -33,26 +35,34 @@
 
 HardwareProperties createHardwareProperties(const InputDeviceContext& context) {
     HardwareProperties props;
-    RawAbsoluteAxisInfo absMtPositionX;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &absMtPositionX);
+    // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub won't
+    // classify a device as a touchpad if they're not present.
+    RawAbsoluteAxisInfo absMtPositionX = context.getAbsoluteAxisInfo(ABS_MT_POSITION_X).value();
     props.left = absMtPositionX.minValue;
     props.right = absMtPositionX.maxValue;
     props.res_x = absMtPositionX.resolution;
 
-    RawAbsoluteAxisInfo absMtPositionY;
-    context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &absMtPositionY);
+    RawAbsoluteAxisInfo absMtPositionY = context.getAbsoluteAxisInfo(ABS_MT_POSITION_Y).value();
     props.top = absMtPositionY.minValue;
     props.bottom = absMtPositionY.maxValue;
     props.res_y = absMtPositionY.resolution;
 
-    RawAbsoluteAxisInfo absMtOrientation;
-    context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &absMtOrientation);
-    props.orientation_minimum = absMtOrientation.minValue;
-    props.orientation_maximum = absMtOrientation.maxValue;
+    if (std::optional<RawAbsoluteAxisInfo> absMtOrientation =
+                context.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
+        absMtOrientation) {
+        props.orientation_minimum = absMtOrientation->minValue;
+        props.orientation_maximum = absMtOrientation->maxValue;
+    } else {
+        props.orientation_minimum = 0;
+        props.orientation_maximum = 0;
+    }
 
-    RawAbsoluteAxisInfo absMtSlot;
-    context.getAbsoluteAxisInfo(ABS_MT_SLOT, &absMtSlot);
-    props.max_finger_cnt = absMtSlot.maxValue - absMtSlot.minValue + 1;
+    if (std::optional<RawAbsoluteAxisInfo> absMtSlot = context.getAbsoluteAxisInfo(ABS_MT_SLOT);
+        absMtSlot) {
+        props.max_finger_cnt = absMtSlot->maxValue - absMtSlot->minValue + 1;
+    } else {
+        props.max_finger_cnt = 1;
+    }
     props.max_touch_cnt = getMaxTouchCount(context);
 
     // T5R2 ("Track 5, Report 2") is a feature of some old Synaptics touchpads that could track 5
@@ -71,9 +81,7 @@
     // are haptic.
     props.is_haptic_pad = false;
 
-    RawAbsoluteAxisInfo absMtPressure;
-    context.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &absMtPressure);
-    props.reports_pressure = absMtPressure.valid;
+    props.reports_pressure = context.hasAbsoluteAxis(ABS_MT_PRESSURE);
     return props;
 }
 
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index b89b7f3..6885adb 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -40,15 +40,15 @@
 }
 
 std::optional<SelfContainedHardwareState> HardwareStateConverter::processRawEvent(
-        const RawEvent* rawEvent) {
+        const RawEvent& rawEvent) {
     std::optional<SelfContainedHardwareState> out;
-    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        out = produceHardwareState(rawEvent->when);
+    if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+        out = produceHardwareState(rawEvent.when);
         mMotionAccumulator.finishSync();
         mMscTimestamp = 0;
     }
-    if (rawEvent->type == EV_MSC && rawEvent->code == MSC_TIMESTAMP) {
-        mMscTimestamp = rawEvent->value;
+    if (rawEvent.type == EV_MSC && rawEvent.code == MSC_TIMESTAMP) {
+        mMscTimestamp = rawEvent.value;
     }
     mCursorButtonAccumulator.process(rawEvent);
     mMotionAccumulator.process(rawEvent);
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index 633448e..148ca5a 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -26,25 +26,20 @@
 #include "accumulator/CursorButtonAccumulator.h"
 #include "accumulator/MultiTouchMotionAccumulator.h"
 #include "accumulator/TouchButtonAccumulator.h"
+#include "include/TouchpadHardwareState.h"
 
+#include "TouchpadHardwareState.h"
 #include "include/gestures.h"
 
 namespace android {
 
-// A HardwareState struct, but bundled with a vector to contain its FingerStates, so you don't have
-// to worry about where that memory is allocated.
-struct SelfContainedHardwareState {
-    HardwareState state;
-    std::vector<FingerState> fingers;
-};
-
 // Converts RawEvents into the HardwareState structs used by the gestures library.
 class HardwareStateConverter {
 public:
     HardwareStateConverter(const InputDeviceContext& deviceContext,
                            MultiTouchMotionAccumulator& motionAccumulator);
 
-    std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
+    std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent& event);
     void reset();
 
 private:
diff --git a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
index 69264f8..f4a5e0d 100644
--- a/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
+++ b/services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp
@@ -90,7 +90,7 @@
     // prefixed with "gestureProp." and have spaces replaced by underscores. So, for example, the
     // configuration key "gestureProp.Palm_Width" refers to the "Palm Width" property.
     const std::string gesturePropPrefix = "gestureProp.";
-    for (const std::string key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) {
+    for (const std::string& key : idcProperties.getKeysWithPrefix(gesturePropPrefix)) {
         std::string propertyName = key.substr(gesturePropPrefix.length());
         for (size_t i = 0; i < propertyName.length(); i++) {
             if (propertyName[i] == '_') {
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
index 255c7eb..5b7cc2d 100644
--- a/services/inputflinger/rust/Android.bp
+++ b/services/inputflinger/rust/Android.bp
@@ -47,6 +47,7 @@
         "liblog_rust",
         "liblogger",
         "libnix",
+        "libinput_rust",
     ],
     host_supported: true,
 }
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
index 2d5039a..e05e8e5 100644
--- a/services/inputflinger/rust/bounce_keys_filter.rs
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -17,12 +17,13 @@
 //! Bounce keys input filter implementation.
 //! Bounce keys is an accessibility feature to aid users who have physical disabilities, that
 //! allows the user to configure the device to ignore rapid, repeated key presses of the same key.
-use crate::input_filter::Filter;
+use crate::input_filter::{Filter, VIRTUAL_KEYBOARD_DEVICE_ID};
 
 use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
 };
+use input::KeyboardType;
 use log::debug;
 use std::collections::{HashMap, HashSet};
 
@@ -42,7 +43,7 @@
     next: Box<dyn Filter + Send + Sync>,
     key_event_map: HashMap<i32, LastUpKeyEvent>,
     blocked_events: Vec<BlockedEvent>,
-    external_devices: HashSet<i32>,
+    supported_devices: HashSet<i32>,
     bounce_key_threshold_ns: i64,
 }
 
@@ -56,7 +57,7 @@
             next,
             key_event_map: HashMap::new(),
             blocked_events: Vec::new(),
-            external_devices: HashSet::new(),
+            supported_devices: HashSet::new(),
             bounce_key_threshold_ns,
         }
     }
@@ -64,7 +65,10 @@
 
 impl Filter for BounceKeysFilter {
     fn notify_key(&mut self, event: &KeyEvent) {
-        if !(self.external_devices.contains(&event.deviceId) && event.source == Source::KEYBOARD) {
+        // Check if it is a supported device and event source contains Source::KEYBOARD
+        if !(self.supported_devices.contains(&event.deviceId)
+            && event.source.0 & Source::KEYBOARD.0 != 0)
+        {
             self.next.notify_key(event);
             return;
         }
@@ -110,10 +114,17 @@
         self.blocked_events.retain(|blocked_event| {
             device_infos.iter().any(|x| blocked_event.device_id == x.deviceId)
         });
-        self.external_devices.clear();
+        self.supported_devices.clear();
         for device_info in device_infos {
-            if device_info.external {
-                self.external_devices.insert(device_info.deviceId);
+            if device_info.deviceId == VIRTUAL_KEYBOARD_DEVICE_ID {
+                continue;
+            }
+            if device_info.keyboardType == KeyboardType::None as i32 {
+                continue;
+            }
+            // Support Alphabetic keyboards and Non-alphabetic external keyboards
+            if device_info.external || device_info.keyboardType == KeyboardType::Alphabetic as i32 {
+                self.supported_devices.insert(device_info.deviceId);
             }
         }
         self.next.notify_devices_changed(device_infos);
@@ -122,16 +133,26 @@
     fn destroy(&mut self) {
         self.next.destroy();
     }
+
+    fn dump(&mut self, dump_str: String) -> String {
+        let mut result = "Bounce Keys filter: \n".to_string();
+        result += &format!("\tthreshold = {:?}ns\n", self.bounce_key_threshold_ns);
+        result += &format!("\tkey_event_map = {:?}\n", self.key_event_map);
+        result += &format!("\tblocked_events = {:?}\n", self.blocked_events);
+        result += &format!("\tsupported_devices = {:?}\n", self.supported_devices);
+        self.next.dump(dump_str + &result)
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use crate::bounce_keys_filter::BounceKeysFilter;
-    use crate::input_filter::{test_filter::TestFilter, Filter};
+    use crate::input_filter::{test_filter::TestFilter, Filter, VIRTUAL_KEYBOARD_DEVICE_ID};
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
 
     static BASE_KEY_EVENT: KeyEvent = KeyEvent {
         id: 1,
@@ -156,6 +177,7 @@
             Box::new(next.clone()),
             1,   /* device_id */
             100, /* threshold */
+            KeyboardType::Alphabetic,
         );
 
         let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
@@ -181,12 +203,103 @@
     }
 
     #[test]
-    fn test_is_notify_key_doesnt_block_for_internal_keyboard() {
+    fn test_is_notify_key_for_tv_remote() {
+        let mut next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            1,   /* device_id */
+            100, /* threshold */
+            KeyboardType::NonAlphabetic,
+        );
+
+        let source = Source(Source::KEYBOARD.0 | Source::DPAD.0);
+        let event = KeyEvent { action: KeyEventAction::DOWN, source, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::UP, source, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        next.clear();
+        let event = KeyEvent { action: KeyEventAction::DOWN, source, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event =
+            KeyEvent { eventTime: 100, action: KeyEventAction::UP, source, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event =
+            KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, source, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_blocks_for_internal_keyboard() {
+        let mut next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            1,   /* device_id */
+            100, /* threshold */
+            KeyboardType::Alphabetic,
+        );
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        next.clear();
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { eventTime: 100, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_doesnt_block_for_internal_non_alphabetic_keyboard() {
         let next = TestFilter::new();
         let mut filter = setup_filter_with_internal_device(
             Box::new(next.clone()),
             1,   /* device_id */
             100, /* threshold */
+            KeyboardType::NonAlphabetic,
+        );
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_doesnt_block_for_virtual_keyboard() {
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            VIRTUAL_KEYBOARD_DEVICE_ID, /* device_id */
+            100,                        /* threshold */
+            KeyboardType::Alphabetic,
         );
 
         let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
@@ -209,6 +322,7 @@
             Box::new(next.clone()),
             1,   /* device_id */
             100, /* threshold */
+            KeyboardType::NonAlphabetic,
         );
 
         let event =
@@ -233,12 +347,60 @@
         let mut filter = setup_filter_with_devices(
             Box::new(next.clone()),
             &[
-                DeviceInfo { deviceId: 1, external: true },
-                DeviceInfo { deviceId: 2, external: true },
+                DeviceInfo {
+                    deviceId: 1,
+                    external: true,
+                    keyboardType: KeyboardType::Alphabetic as i32,
+                },
+                DeviceInfo {
+                    deviceId: 2,
+                    external: true,
+                    keyboardType: KeyboardType::Alphabetic as i32,
+                },
             ],
             100, /* threshold */
         );
 
+        // Bounce key scenario on the external keyboard
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+
+        next.clear();
+        let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert!(next.last_event().is_none());
+
+        let event = KeyEvent { deviceId: 2, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+        filter.notify_key(&event);
+        assert_eq!(next.last_event().unwrap(), event);
+    }
+
+    #[test]
+    fn test_is_notify_key_for_external_and_internal_alphabetic_keyboards() {
+        let mut next = TestFilter::new();
+        let mut filter = setup_filter_with_devices(
+            Box::new(next.clone()),
+            &[
+                DeviceInfo {
+                    deviceId: 1,
+                    external: false,
+                    keyboardType: KeyboardType::Alphabetic as i32,
+                },
+                DeviceInfo {
+                    deviceId: 2,
+                    external: true,
+                    keyboardType: KeyboardType::Alphabetic as i32,
+                },
+            ],
+            100, /* threshold */
+        );
+
+        // Bounce key scenario on the internal keyboard
         let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
         filter.notify_key(&event);
         assert_eq!(next.last_event().unwrap(), event);
@@ -261,10 +423,15 @@
         next: Box<dyn Filter + Send + Sync>,
         device_id: i32,
         threshold: i64,
+        keyboard_type: KeyboardType,
     ) -> BounceKeysFilter {
         setup_filter_with_devices(
             next,
-            &[DeviceInfo { deviceId: device_id, external: true }],
+            &[DeviceInfo {
+                deviceId: device_id,
+                external: true,
+                keyboardType: keyboard_type as i32,
+            }],
             threshold,
         )
     }
@@ -273,10 +440,15 @@
         next: Box<dyn Filter + Send + Sync>,
         device_id: i32,
         threshold: i64,
+        keyboard_type: KeyboardType,
     ) -> BounceKeysFilter {
         setup_filter_with_devices(
             next,
-            &[DeviceInfo { deviceId: device_id, external: false }],
+            &[DeviceInfo {
+                deviceId: device_id,
+                external: false,
+                keyboardType: keyboard_type as i32,
+            }],
             threshold,
         )
     }
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index a544fa3..e221244 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -31,14 +31,19 @@
 use crate::input_filter_thread::InputFilterThread;
 use crate::slow_keys_filter::SlowKeysFilter;
 use crate::sticky_keys_filter::StickyKeysFilter;
+use input::ModifierState;
 use log::{error, info};
 use std::sync::{Arc, Mutex, RwLock};
 
+/// Virtual keyboard device ID
+pub const VIRTUAL_KEYBOARD_DEVICE_ID: i32 = -1;
+
 /// Interface for all the sub input filters
 pub trait Filter {
     fn notify_key(&mut self, event: &KeyEvent);
     fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
     fn destroy(&mut self);
+    fn dump(&mut self, dump_str: String) -> String;
 }
 
 struct InputFilterState {
@@ -118,18 +123,30 @@
                     self.input_filter_thread.clone(),
                 ));
                 state.enabled = true;
-                info!("Slow keys filter is installed");
+                info!(
+                    "Slow keys filter is installed, threshold = {:?}ns",
+                    config.slowKeysThresholdNs
+                );
             }
             if config.bounceKeysThresholdNs > 0 {
                 first_filter =
                     Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
                 state.enabled = true;
-                info!("Bounce keys filter is installed");
+                info!(
+                    "Bounce keys filter is installed, threshold = {:?}ns",
+                    config.bounceKeysThresholdNs
+                );
             }
             state.first_filter = first_filter;
         }
         Result::Ok(())
     }
+
+    fn dumpFilter(&self) -> binder::Result<String> {
+        let first_filter = &mut self.state.lock().unwrap().first_filter;
+        let dump_str = first_filter.dump(String::new());
+        Result::Ok(dump_str)
+    }
 }
 
 struct BaseFilter {
@@ -157,6 +174,11 @@
     fn destroy(&mut self) {
         // do nothing
     }
+
+    fn dump(&mut self, dump_str: String) -> String {
+        // do nothing
+        dump_str
+    }
 }
 
 /// This struct wraps around IInputFilterCallbacks restricting access to only
@@ -169,12 +191,15 @@
         Self(callbacks)
     }
 
-    pub fn modifier_state_changed(&self, modifier_state: u32, locked_modifier_state: u32) {
-        let _ = self
-            .0
-            .read()
-            .unwrap()
-            .onModifierStateChanged(modifier_state as i32, locked_modifier_state as i32);
+    pub fn modifier_state_changed(
+        &self,
+        modifier_state: ModifierState,
+        locked_modifier_state: ModifierState,
+    ) {
+        let _ = self.0.read().unwrap().onModifierStateChanged(
+            modifier_state.bits() as i32,
+            locked_modifier_state.bits() as i32,
+        );
     }
 }
 
@@ -210,6 +235,7 @@
         InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
         KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
     use std::sync::{Arc, RwLock};
 
     #[test]
@@ -252,7 +278,11 @@
             Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks)))),
         );
         assert!(input_filter
-            .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
+            .notifyInputDevicesChanged(&[DeviceInfo {
+                deviceId: 0,
+                external: true,
+                keyboardType: KeyboardType::None as i32
+            }])
             .is_ok());
         assert!(test_filter.is_device_changed_called());
     }
@@ -385,6 +415,10 @@
         fn destroy(&mut self) {
             self.inner().is_destroy_called = true;
         }
+        fn dump(&mut self, dump_str: String) -> String {
+            // do nothing
+            dump_str
+        }
     }
 }
 
@@ -396,14 +430,17 @@
         IInputThread::{BnInputThread, IInputThread, IInputThreadCallback::IInputThreadCallback},
         KeyEvent::KeyEvent,
     };
-    use std::sync::{Arc, RwLock, RwLockWriteGuard};
+    use input::ModifierState;
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc, RwLock, RwLockWriteGuard};
+    use std::time::Duration;
 
     #[derive(Default)]
     struct TestCallbacksInner {
-        last_modifier_state: u32,
-        last_locked_modifier_state: u32,
+        last_modifier_state: ModifierState,
+        last_locked_modifier_state: ModifierState,
         last_event: Option<KeyEvent>,
-        test_thread: Option<TestThread>,
+        test_thread: Option<FakeCppThread>,
     }
 
     #[derive(Default, Clone)]
@@ -426,25 +463,21 @@
 
         pub fn clear(&mut self) {
             self.inner().last_event = None;
-            self.inner().last_modifier_state = 0;
-            self.inner().last_locked_modifier_state = 0;
+            self.inner().last_modifier_state = ModifierState::None;
+            self.inner().last_locked_modifier_state = ModifierState::None;
         }
 
-        pub fn get_last_modifier_state(&self) -> u32 {
+        pub fn get_last_modifier_state(&self) -> ModifierState {
             self.0.read().unwrap().last_modifier_state
         }
 
-        pub fn get_last_locked_modifier_state(&self) -> u32 {
+        pub fn get_last_locked_modifier_state(&self) -> ModifierState {
             self.0.read().unwrap().last_locked_modifier_state
         }
 
-        pub fn is_thread_created(&self) -> bool {
-            self.0.read().unwrap().test_thread.is_some()
-        }
-
-        pub fn is_thread_finished(&self) -> bool {
+        pub fn is_thread_running(&self) -> bool {
             if let Some(test_thread) = &self.0.read().unwrap().test_thread {
-                return test_thread.is_finish_called();
+                return test_thread.is_running();
             }
             false
         }
@@ -461,48 +494,110 @@
             modifier_state: i32,
             locked_modifier_state: i32,
         ) -> std::result::Result<(), binder::Status> {
-            self.inner().last_modifier_state = modifier_state as u32;
-            self.inner().last_locked_modifier_state = locked_modifier_state as u32;
+            self.inner().last_modifier_state =
+                ModifierState::from_bits(modifier_state as u32).unwrap();
+            self.inner().last_locked_modifier_state =
+                ModifierState::from_bits(locked_modifier_state as u32).unwrap();
             Result::Ok(())
         }
 
         fn createInputFilterThread(
             &self,
-            _callback: &Strong<dyn IInputThreadCallback>,
+            callback: &Strong<dyn IInputThreadCallback>,
         ) -> std::result::Result<Strong<dyn IInputThread>, binder::Status> {
-            let test_thread = TestThread::new();
+            let test_thread = FakeCppThread::new(callback.clone());
+            test_thread.start_looper();
             self.inner().test_thread = Some(test_thread.clone());
             Result::Ok(BnInputThread::new_binder(test_thread, BinderFeatures::default()))
         }
     }
 
     #[derive(Default)]
-    struct TestThreadInner {
-        is_finish_called: bool,
+    struct FakeCppThreadInner {
+        join_handle: Option<std::thread::JoinHandle<()>>,
     }
 
-    #[derive(Default, Clone)]
-    struct TestThread(Arc<RwLock<TestThreadInner>>);
+    #[derive(Clone)]
+    struct FakeCppThread {
+        callback: Arc<RwLock<Strong<dyn IInputThreadCallback>>>,
+        inner: Arc<RwLock<FakeCppThreadInner>>,
+        exit_flag: Arc<AtomicBool>,
+    }
 
-    impl Interface for TestThread {}
+    impl Interface for FakeCppThread {}
 
-    impl TestThread {
-        pub fn new() -> Self {
-            Default::default()
+    impl FakeCppThread {
+        pub fn new(callback: Strong<dyn IInputThreadCallback>) -> Self {
+            let thread = Self {
+                callback: Arc::new(RwLock::new(callback)),
+                inner: Arc::new(RwLock::new(FakeCppThreadInner { join_handle: None })),
+                exit_flag: Arc::new(AtomicBool::new(true)),
+            };
+            thread.create_looper();
+            thread
         }
 
-        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
-            self.0.write().unwrap()
+        fn inner(&self) -> RwLockWriteGuard<'_, FakeCppThreadInner> {
+            self.inner.write().unwrap()
         }
 
-        pub fn is_finish_called(&self) -> bool {
-            self.0.read().unwrap().is_finish_called
+        fn create_looper(&self) {
+            let clone = self.clone();
+            let join_handle = std::thread::Builder::new()
+                .name("fake_cpp_thread".to_string())
+                .spawn(move || loop {
+                    if !clone.exit_flag.load(Ordering::Relaxed) {
+                        clone.loop_once();
+                    }
+                })
+                .unwrap();
+            self.inner().join_handle = Some(join_handle);
+            // Sleep until the looper thread starts
+            std::thread::sleep(Duration::from_millis(10));
+        }
+
+        pub fn start_looper(&self) {
+            self.exit_flag.store(false, Ordering::Relaxed);
+        }
+
+        pub fn stop_looper(&self) {
+            self.exit_flag.store(true, Ordering::Relaxed);
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+        }
+
+        pub fn is_running(&self) -> bool {
+            !self.exit_flag.load(Ordering::Relaxed)
+        }
+
+        fn loop_once(&self) {
+            let _ = self.callback.read().unwrap().loopOnce();
         }
     }
 
-    impl IInputThread for TestThread {
+    impl IInputThread for FakeCppThread {
         fn finish(&self) -> binder::Result<()> {
-            self.inner().is_finish_called = true;
+            self.stop_looper();
+            Result::Ok(())
+        }
+
+        fn wake(&self) -> binder::Result<()> {
+            if let Some(join_handle) = &self.inner.read().unwrap().join_handle {
+                join_handle.thread().unpark();
+            }
+            Result::Ok(())
+        }
+
+        fn sleepUntil(&self, wake_up_time: i64) -> binder::Result<()> {
+            let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+            if wake_up_time == i64::MAX {
+                std::thread::park();
+            } else {
+                let duration_now = Duration::from_nanos(now as u64);
+                let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
+                std::thread::park_timeout(duration_wake_up - duration_now);
+            }
             Result::Ok(())
         }
     }
diff --git a/services/inputflinger/rust/input_filter_thread.rs b/services/inputflinger/rust/input_filter_thread.rs
index 2d503ae..34f9b25 100644
--- a/services/inputflinger/rust/input_filter_thread.rs
+++ b/services/inputflinger/rust/input_filter_thread.rs
@@ -33,8 +33,6 @@
 use log::{debug, error};
 use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
 use std::sync::{Arc, RwLock, RwLockWriteGuard};
-use std::time::Duration;
-use std::{thread, thread::Thread};
 
 /// Interface to receive callback from Input filter thread
 pub trait ThreadCallback {
@@ -54,15 +52,18 @@
     thread_creator: InputFilterThreadCreator,
     thread_callback_handler: ThreadCallbackHandler,
     inner: Arc<RwLock<InputFilterThreadInner>>,
+    looper: Arc<RwLock<Looper>>,
 }
 
 struct InputFilterThreadInner {
-    cpp_thread: Option<Strong<dyn IInputThread>>,
-    looper: Option<Thread>,
     next_timeout: i64,
     is_finishing: bool,
 }
 
+struct Looper {
+    cpp_thread: Option<Strong<dyn IInputThread>>,
+}
+
 impl InputFilterThread {
     /// Create a new InputFilterThread instance.
     /// NOTE: This will create a new thread. Clone the existing instance to reuse the same thread.
@@ -71,11 +72,10 @@
             thread_creator,
             thread_callback_handler: ThreadCallbackHandler::new(),
             inner: Arc::new(RwLock::new(InputFilterThreadInner {
-                cpp_thread: None,
-                looper: None,
                 next_timeout: i64::MAX,
                 is_finishing: false,
             })),
+            looper: Arc::new(RwLock::new(Looper { cpp_thread: None })),
         }
     }
 
@@ -83,12 +83,17 @@
     /// time on the input filter thread.
     /// {@see ThreadCallback.notify_timeout_expired(...)}
     pub fn request_timeout_at_time(&self, when_nanos: i64) {
-        let filter_thread = &mut self.filter_thread();
-        if when_nanos < filter_thread.next_timeout {
-            filter_thread.next_timeout = when_nanos;
-            if let Some(looper) = &filter_thread.looper {
-                looper.unpark();
+        let mut need_wake = false;
+        {
+            // acquire filter lock
+            let filter_thread = &mut self.filter_thread();
+            if when_nanos < filter_thread.next_timeout {
+                filter_thread.next_timeout = when_nanos;
+                need_wake = true;
             }
+        } // release filter lock
+        if need_wake {
+            self.wake();
         }
     }
 
@@ -120,29 +125,36 @@
 
     fn start(&self) {
         debug!("InputFilterThread: start thread");
-        let filter_thread = &mut self.filter_thread();
-        if filter_thread.cpp_thread.is_none() {
-            filter_thread.cpp_thread = Some(self.thread_creator.create(
-                &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
-            ));
-            filter_thread.looper = None;
-            filter_thread.is_finishing = false;
-        }
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if looper.cpp_thread.is_none() {
+                looper.cpp_thread = Some(self.thread_creator.create(
+                    &BnInputThreadCallback::new_binder(self.clone(), BinderFeatures::default()),
+                ));
+            }
+        } // release looper lock
+        self.set_finishing(false);
     }
 
     fn stop(&self) {
         debug!("InputFilterThread: stop thread");
+        self.set_finishing(true);
+        self.wake();
+        {
+            // acquire looper lock
+            let looper = &mut self.looper();
+            if let Some(cpp_thread) = &looper.cpp_thread {
+                let _ = cpp_thread.finish();
+            }
+            // Clear all references
+            looper.cpp_thread = None;
+        } // release looper lock
+    }
+
+    fn set_finishing(&self, is_finishing: bool) {
         let filter_thread = &mut self.filter_thread();
-        filter_thread.is_finishing = true;
-        if let Some(looper) = &filter_thread.looper {
-            looper.unpark();
-        }
-        if let Some(cpp_thread) = &filter_thread.cpp_thread {
-            let _ = cpp_thread.finish();
-        }
-        // Clear all references
-        filter_thread.cpp_thread = None;
-        filter_thread.looper = None;
+        filter_thread.is_finishing = is_finishing;
     }
 
     fn loop_once(&self, now: i64) {
@@ -163,25 +175,34 @@
                     wake_up_time = filter_thread.next_timeout;
                 }
             }
-            if filter_thread.looper.is_none() {
-                filter_thread.looper = Some(std::thread::current());
-            }
         } // release thread lock
         if timeout_expired {
             self.thread_callback_handler.notify_timeout_expired(now);
         }
-        if wake_up_time == i64::MAX {
-            thread::park();
-        } else {
-            let duration_now = Duration::from_nanos(now as u64);
-            let duration_wake_up = Duration::from_nanos(wake_up_time as u64);
-            thread::park_timeout(duration_wake_up - duration_now);
-        }
+        self.sleep_until(wake_up_time);
     }
 
     fn filter_thread(&self) -> RwLockWriteGuard<'_, InputFilterThreadInner> {
         self.inner.write().unwrap()
     }
+
+    fn sleep_until(&self, when_nanos: i64) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.sleepUntil(when_nanos);
+        }
+    }
+
+    fn wake(&self) {
+        let looper = self.looper.read().unwrap();
+        if let Some(cpp_thread) = &looper.cpp_thread {
+            let _ = cpp_thread.wake();
+        }
+    }
+
+    fn looper(&self) -> RwLockWriteGuard<'_, Looper> {
+        self.looper.write().unwrap()
+    }
 }
 
 impl Interface for InputFilterThread {}
@@ -252,165 +273,64 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::test_callbacks::TestCallbacks;
-    use crate::input_filter_thread::{
-        test_thread::TestThread, test_thread_callback::TestThreadCallback,
-    };
+    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
+    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
+    use binder::Strong;
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
 
     #[test]
     fn test_register_callback_creates_cpp_thread() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback);
-        assert!(test_callbacks.is_thread_created());
+        test_thread.register_thread_callback(Box::new(test_thread_callback));
+        assert!(test_callbacks.is_thread_running());
     }
 
     #[test]
     fn test_unregister_callback_finishes_cpp_thread() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.unregister_thread_callback(test_thread_callback);
-        assert!(test_callbacks.is_thread_finished());
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
+        test_thread.unregister_thread_callback(Box::new(test_thread_callback));
+        assert!(!test_callbacks.is_thread_running());
     }
 
     #[test]
     fn test_notify_timeout_called_after_timeout_expired() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.start_looper();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
 
-        test_thread.request_timeout_at_time(500);
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 10) * 1000000);
 
-        test_thread.move_time_forward(500);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_millis(100));
         assert!(test_thread_callback.is_notify_timeout_called());
     }
 
     #[test]
     fn test_notify_timeout_not_called_before_timeout_expired() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let test_thread_callback = TestThreadCallback::new();
-        test_thread.register_thread_callback(test_thread_callback.clone());
-        test_thread.start_looper();
+        test_thread.register_thread_callback(Box::new(test_thread_callback.clone()));
 
-        test_thread.request_timeout_at_time(500);
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_milliseconds();
+        test_thread.request_timeout_at_time((now + 100) * 1000000);
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_millis(10));
         assert!(!test_thread_callback.is_notify_timeout_called());
     }
-}
 
-#[cfg(test)]
-pub mod test_thread {
-
-    use crate::input_filter::{test_callbacks::TestCallbacks, InputFilterThreadCreator};
-    use crate::input_filter_thread::{test_thread_callback::TestThreadCallback, InputFilterThread};
-    use binder::Strong;
-    use std::sync::{
-        atomic::AtomicBool, atomic::AtomicI64, atomic::Ordering, Arc, RwLock, RwLockWriteGuard,
-    };
-    use std::time::Duration;
-
-    #[derive(Clone)]
-    pub struct TestThread {
-        input_thread: InputFilterThread,
-        inner: Arc<RwLock<TestThreadInner>>,
-        exit_flag: Arc<AtomicBool>,
-        now: Arc<AtomicI64>,
-    }
-
-    struct TestThreadInner {
-        join_handle: Option<std::thread::JoinHandle<()>>,
-    }
-
-    impl TestThread {
-        pub fn new(callbacks: TestCallbacks) -> TestThread {
-            Self {
-                input_thread: InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(
-                    RwLock::new(Strong::new(Box::new(callbacks))),
-                ))),
-                inner: Arc::new(RwLock::new(TestThreadInner { join_handle: None })),
-                exit_flag: Arc::new(AtomicBool::new(false)),
-                now: Arc::new(AtomicI64::new(0)),
-            }
-        }
-
-        fn inner(&self) -> RwLockWriteGuard<'_, TestThreadInner> {
-            self.inner.write().unwrap()
-        }
-
-        pub fn get_input_thread(&self) -> InputFilterThread {
-            self.input_thread.clone()
-        }
-
-        pub fn register_thread_callback(&self, thread_callback: TestThreadCallback) {
-            self.input_thread.register_thread_callback(Box::new(thread_callback));
-        }
-
-        pub fn unregister_thread_callback(&self, thread_callback: TestThreadCallback) {
-            self.input_thread.unregister_thread_callback(Box::new(thread_callback));
-        }
-
-        pub fn start_looper(&self) {
-            self.exit_flag.store(false, Ordering::Relaxed);
-            let clone = self.clone();
-            let join_handle = std::thread::Builder::new()
-                .name("test_thread".to_string())
-                .spawn(move || {
-                    while !clone.exit_flag.load(Ordering::Relaxed) {
-                        clone.loop_once();
-                    }
-                })
-                .unwrap();
-            self.inner().join_handle = Some(join_handle);
-            // Sleep until the looper thread starts
-            std::thread::sleep(Duration::from_millis(10));
-        }
-
-        pub fn stop_looper(&self) {
-            self.exit_flag.store(true, Ordering::Relaxed);
-            {
-                let mut inner = self.inner();
-                if let Some(join_handle) = &inner.join_handle {
-                    join_handle.thread().unpark();
-                }
-                inner.join_handle.take().map(std::thread::JoinHandle::join);
-                inner.join_handle = None;
-            }
-            self.exit_flag.store(false, Ordering::Relaxed);
-        }
-
-        pub fn move_time_forward(&self, value: i64) {
-            let _ = self.now.fetch_add(value, Ordering::Relaxed);
-            self.dispatch_next();
-        }
-
-        pub fn dispatch_next(&self) {
-            if let Some(join_handle) = &self.inner().join_handle {
-                join_handle.thread().unpark();
-            }
-            // Sleep until the looper thread runs a loop
-            std::thread::sleep(Duration::from_millis(10));
-        }
-
-        fn loop_once(&self) {
-            self.input_thread.loop_once(self.now.load(Ordering::Relaxed));
-        }
-
-        pub fn request_timeout_at_time(&self, when_nanos: i64) {
-            self.input_thread.request_timeout_at_time(when_nanos);
-        }
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
     }
 }
 
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 09fbf40..8830aac 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -18,12 +18,13 @@
 //! Slow keys is an accessibility feature to aid users who have physical disabilities, that allows
 //! the user to specify the duration for which one must press-and-hold a key before the system
 //! accepts the keypress.
-use crate::input_filter::Filter;
+use crate::input_filter::{Filter, VIRTUAL_KEYBOARD_DEVICE_ID};
 use crate::input_filter_thread::{InputFilterThread, ThreadCallback};
 use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
 };
+use input::KeyboardType;
 use log::debug;
 use std::collections::HashSet;
 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -41,7 +42,7 @@
 struct SlowKeysFilterInner {
     next: Box<dyn Filter + Send + Sync>,
     slow_key_threshold_ns: i64,
-    external_devices: HashSet<i32>,
+    supported_devices: HashSet<i32>,
     // This tracks KeyEvents that are blocked by Slow keys filter and will be passed through if the
     // press duration exceeds the slow keys threshold.
     pending_down_events: Vec<KeyEvent>,
@@ -65,7 +66,7 @@
         let filter = Self(Arc::new(RwLock::new(SlowKeysFilterInner {
             next,
             slow_key_threshold_ns,
-            external_devices: HashSet::new(),
+            supported_devices: HashSet::new(),
             pending_down_events: Vec::new(),
             ongoing_down_events: Vec::new(),
             input_filter_thread: input_filter_thread.clone(),
@@ -98,8 +99,8 @@
         {
             // acquire write lock
             let mut slow_filter = self.write_inner();
-            if !(slow_filter.external_devices.contains(&event.deviceId)
-                && event.source == Source::KEYBOARD)
+            if !(slow_filter.supported_devices.contains(&event.deviceId)
+                && event.source.0 & Source::KEYBOARD.0 != 0)
             {
                 slow_filter.next.notify_key(event);
                 return;
@@ -164,10 +165,17 @@
         slow_filter
             .ongoing_down_events
             .retain(|event| device_infos.iter().any(|x| event.device_id == x.deviceId));
-        slow_filter.external_devices.clear();
+        slow_filter.supported_devices.clear();
         for device_info in device_infos {
-            if device_info.external {
-                slow_filter.external_devices.insert(device_info.deviceId);
+            if device_info.deviceId == VIRTUAL_KEYBOARD_DEVICE_ID {
+                continue;
+            }
+            if device_info.keyboardType == KeyboardType::None as i32 {
+                continue;
+            }
+            // Support Alphabetic keyboards and Non-alphabetic external keyboards
+            if device_info.external || device_info.keyboardType == KeyboardType::Alphabetic as i32 {
+                slow_filter.supported_devices.insert(device_info.deviceId);
             }
         }
         slow_filter.next.notify_devices_changed(device_infos);
@@ -178,6 +186,16 @@
         slow_filter.input_filter_thread.unregister_thread_callback(Box::new(self.clone()));
         slow_filter.next.destroy();
     }
+
+    fn dump(&mut self, dump_str: String) -> String {
+        let mut slow_filter = self.write_inner();
+        let mut result = "Slow Keys filter: \n".to_string();
+        result += &format!("\tthreshold = {:?}ns\n", slow_filter.slow_key_threshold_ns);
+        result += &format!("\tongoing_down_events = {:?}\n", slow_filter.ongoing_down_events);
+        result += &format!("\tpending_down_events = {:?}\n", slow_filter.pending_down_events);
+        result += &format!("\tsupported_devices = {:?}\n", slow_filter.supported_devices);
+        slow_filter.next.dump(dump_str + &result)
+    }
 }
 
 impl ThreadCallback for SlowKeysFilter {
@@ -207,13 +225,20 @@
 
 #[cfg(test)]
 mod tests {
-    use crate::input_filter::{test_callbacks::TestCallbacks, test_filter::TestFilter, Filter};
-    use crate::input_filter_thread::test_thread::TestThread;
+    use crate::input_filter::{
+        test_callbacks::TestCallbacks, test_filter::TestFilter, Filter, InputFilterThreadCreator,
+    };
+    use crate::input_filter_thread::InputFilterThread;
     use crate::slow_keys_filter::{SlowKeysFilter, POLICY_FLAG_DISABLE_KEY_REPEAT};
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+    use binder::Strong;
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
+    use nix::{sys::time::TimeValLike, time::clock_gettime, time::ClockId};
+    use std::sync::{Arc, RwLock};
+    use std::time::Duration;
 
     static BASE_KEY_EVENT: KeyEvent = KeyEvent {
         id: 1,
@@ -231,18 +256,20 @@
         metaState: 0,
     };
 
+    static SLOW_KEYS_THRESHOLD_NS: i64 = 100 * 1000000; // 100 ms
+
     #[test]
-    fn test_is_notify_key_for_internal_keyboard_not_blocked() {
+    fn test_is_notify_key_for_internal_non_alphabetic_keyboard_not_blocked() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_internal_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::NonAlphabetic,
         );
-        test_thread.start_looper();
 
         let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
         filter.notify_key(&event);
@@ -252,15 +279,15 @@
     #[test]
     fn test_is_notify_key_for_external_stylus_not_blocked() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::NonAlphabetic,
         );
-        test_thread.start_looper();
 
         let event =
             KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
@@ -269,124 +296,278 @@
     }
 
     #[test]
-    fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
+    fn test_notify_key_for_tv_remote_when_key_pressed_for_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::NonAlphabetic,
         );
-        test_thread.start_looper();
-
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
+        let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        let source = Source(Source::KEYBOARD.0 | Source::DPAD.0);
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: down_time,
+            eventTime: down_time,
+            source,
+            ..BASE_KEY_EVENT
+        });
         assert!(next.last_event().is_none());
-        test_thread.dispatch_next();
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
         assert_eq!(
             next.last_event().unwrap(),
             KeyEvent {
                 action: KeyEventAction::DOWN,
-                downTime: 100,
-                eventTime: 100,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                source,
                 policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
                 ..BASE_KEY_EVENT
             }
         );
+
+        let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: down_time,
+            eventTime: up_time,
+            source,
+            ..BASE_KEY_EVENT
+        });
+
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::UP,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: up_time,
+                source,
+                ..BASE_KEY_EVENT
+            }
+        );
+    }
+
+    #[test]
+    fn test_notify_key_for_internal_alphabetic_keyboard_when_key_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_internal_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
+        );
+        let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: down_time,
+            eventTime: down_time,
+            ..BASE_KEY_EVENT
+        });
+        assert!(next.last_event().is_none());
+
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::DOWN,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
+                ..BASE_KEY_EVENT
+            }
+        );
+
+        let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: down_time,
+            eventTime: up_time,
+            ..BASE_KEY_EVENT
+        });
+
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::UP,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: up_time,
+                ..BASE_KEY_EVENT
+            }
+        );
+    }
+
+    #[test]
+    fn test_notify_key_for_external_keyboard_when_key_pressed_for_threshold_time() {
+        let test_callbacks = TestCallbacks::new();
+        let test_thread = get_thread(test_callbacks.clone());
+        let next = TestFilter::new();
+        let mut filter = setup_filter_with_external_device(
+            Box::new(next.clone()),
+            test_thread.clone(),
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
+        );
+        let down_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: down_time,
+            eventTime: down_time,
+            ..BASE_KEY_EVENT
+        });
+        assert!(next.last_event().is_none());
+
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::DOWN,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                policyFlags: POLICY_FLAG_DISABLE_KEY_REPEAT,
+                ..BASE_KEY_EVENT
+            }
+        );
+
+        let up_time = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: down_time,
+            eventTime: up_time,
+            ..BASE_KEY_EVENT
+        });
+
+        assert_eq!(
+            next.last_event().unwrap(),
+            KeyEvent {
+                action: KeyEventAction::UP,
+                downTime: down_time + SLOW_KEYS_THRESHOLD_NS,
+                eventTime: up_time,
+                ..BASE_KEY_EVENT
+            }
+        );
     }
 
     #[test]
     fn test_notify_key_for_external_keyboard_when_key_not_pressed_for_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
         );
-        test_thread.start_looper();
+        let mut now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
+        std::thread::sleep(Duration::from_nanos(SLOW_KEYS_THRESHOLD_NS as u64 / 2));
 
-        test_thread.move_time_forward(10);
+        now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::UP,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT });
-        test_thread.dispatch_next();
-
-        test_thread.stop_looper();
         assert!(next.last_event().is_none());
     }
 
     #[test]
     fn test_notify_key_for_external_keyboard_when_device_removed_before_threshold_time() {
         let test_callbacks = TestCallbacks::new();
-        let test_thread = TestThread::new(test_callbacks.clone());
+        let test_thread = get_thread(test_callbacks.clone());
         let next = TestFilter::new();
         let mut filter = setup_filter_with_external_device(
             Box::new(next.clone()),
             test_thread.clone(),
-            1,   /* device_id */
-            100, /* threshold */
+            1, /* device_id */
+            SLOW_KEYS_THRESHOLD_NS,
+            KeyboardType::Alphabetic,
         );
-        test_thread.start_looper();
 
-        filter.notify_key(&KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT });
-        assert!(next.last_event().is_none());
-        test_thread.dispatch_next();
+        let now = clock_gettime(ClockId::CLOCK_MONOTONIC).unwrap().num_nanoseconds();
+        filter.notify_key(&KeyEvent {
+            action: KeyEventAction::DOWN,
+            downTime: now,
+            eventTime: now,
+            ..BASE_KEY_EVENT
+        });
 
         filter.notify_devices_changed(&[]);
-        test_thread.dispatch_next();
+        std::thread::sleep(Duration::from_nanos(2 * SLOW_KEYS_THRESHOLD_NS as u64));
 
-        test_thread.move_time_forward(100);
-
-        test_thread.stop_looper();
         assert!(next.last_event().is_none());
     }
 
     fn setup_filter_with_external_device(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         device_id: i32,
         threshold: i64,
+        keyboard_type: KeyboardType,
     ) -> SlowKeysFilter {
         setup_filter_with_devices(
             next,
             test_thread,
-            &[DeviceInfo { deviceId: device_id, external: true }],
+            &[DeviceInfo {
+                deviceId: device_id,
+                external: true,
+                keyboardType: keyboard_type as i32,
+            }],
             threshold,
         )
     }
 
     fn setup_filter_with_internal_device(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         device_id: i32,
         threshold: i64,
+        keyboard_type: KeyboardType,
     ) -> SlowKeysFilter {
         setup_filter_with_devices(
             next,
             test_thread,
-            &[DeviceInfo { deviceId: device_id, external: false }],
+            &[DeviceInfo {
+                deviceId: device_id,
+                external: false,
+                keyboardType: keyboard_type as i32,
+            }],
             threshold,
         )
     }
 
     fn setup_filter_with_devices(
         next: Box<dyn Filter + Send + Sync>,
-        test_thread: TestThread,
+        test_thread: InputFilterThread,
         devices: &[DeviceInfo],
         threshold: i64,
     ) -> SlowKeysFilter {
-        let mut filter = SlowKeysFilter::new(next, threshold, test_thread.get_input_thread());
+        let mut filter = SlowKeysFilter::new(next, threshold, test_thread);
         filter.notify_devices_changed(devices);
         filter
     }
+
+    fn get_thread(callbacks: TestCallbacks) -> InputFilterThread {
+        InputFilterThread::new(InputFilterThreadCreator::new(Arc::new(RwLock::new(Strong::new(
+            Box::new(callbacks),
+        )))))
+    }
 }
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 6c2277c..161a5fc 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -23,6 +23,7 @@
 use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
     DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
 };
+use input::ModifierState;
 use std::collections::HashSet;
 
 // Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
@@ -40,20 +41,6 @@
 const KEYCODE_FUNCTION: i32 = 119;
 const KEYCODE_NUM_LOCK: i32 = 143;
 
-// Modifier states: values are from /frameworks/native/include/android/input.h
-const META_ALT_ON: u32 = 0x02;
-const META_ALT_LEFT_ON: u32 = 0x10;
-const META_ALT_RIGHT_ON: u32 = 0x20;
-const META_SHIFT_ON: u32 = 0x01;
-const META_SHIFT_LEFT_ON: u32 = 0x40;
-const META_SHIFT_RIGHT_ON: u32 = 0x80;
-const META_CTRL_ON: u32 = 0x1000;
-const META_CTRL_LEFT_ON: u32 = 0x2000;
-const META_CTRL_RIGHT_ON: u32 = 0x4000;
-const META_META_ON: u32 = 0x10000;
-const META_META_LEFT_ON: u32 = 0x20000;
-const META_META_RIGHT_ON: u32 = 0x40000;
-
 pub struct StickyKeysFilter {
     next: Box<dyn Filter + Send + Sync>,
     listener: ModifierStateListener,
@@ -61,11 +48,11 @@
     contributing_devices: HashSet<i32>,
     /// State describing the current enabled modifiers. This contain both locked and non-locked
     /// modifier state bits.
-    modifier_state: u32,
+    modifier_state: ModifierState,
     /// State describing the current locked modifiers. These modifiers will not be cleared on a
     /// non-modifier key press. They will be cleared only if the locked modifier key is pressed
     /// again.
-    locked_modifier_state: u32,
+    locked_modifier_state: ModifierState,
 }
 
 impl StickyKeysFilter {
@@ -78,8 +65,8 @@
             next,
             listener,
             contributing_devices: HashSet::new(),
-            modifier_state: 0,
-            locked_modifier_state: 0,
+            modifier_state: ModifierState::None,
+            locked_modifier_state: ModifierState::None,
         }
     }
 }
@@ -93,12 +80,12 @@
             // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
             // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
             // the KeyEvent.
-            let old_modifier_state = event.metaState as u32;
+            let old_modifier_state = ModifierState::from_bits(event.metaState as u32).unwrap();
             let mut new_event = *event;
             // Send the current modifier state with the key event before clearing non-locked
             // modifier state
             new_event.metaState =
-                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state) as i32;
+                (clear_ephemeral_modifier_state(old_modifier_state) | modifier_state).bits() as i32;
             self.next.notify_key(&new_event);
             if up && !is_modifier_key(event.keyCode) {
                 modifier_state =
@@ -110,10 +97,10 @@
             // If ephemeral modifier key, capture the key and update the sticky modifier states
             let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
             let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
-            if locked_modifier_state & modifier_key_mask != 0 {
+            if locked_modifier_state & modifier_key_mask != ModifierState::None {
                 locked_modifier_state &= !symmetrical_modifier_key_mask;
                 modifier_state &= !symmetrical_modifier_key_mask;
-            } else if modifier_key_mask & modifier_state != 0 {
+            } else if modifier_key_mask & modifier_state != ModifierState::None {
                 locked_modifier_state |= modifier_key_mask;
                 modifier_state =
                     (modifier_state & !symmetrical_modifier_key_mask) | modifier_key_mask;
@@ -134,11 +121,12 @@
         // Clear state if all contributing devices removed
         self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
         if self.contributing_devices.is_empty()
-            && (self.modifier_state != 0 || self.locked_modifier_state != 0)
+            && (self.modifier_state != ModifierState::None
+                || self.locked_modifier_state != ModifierState::None)
         {
-            self.modifier_state = 0;
-            self.locked_modifier_state = 0;
-            self.listener.modifier_state_changed(0, 0);
+            self.modifier_state = ModifierState::None;
+            self.locked_modifier_state = ModifierState::None;
+            self.listener.modifier_state_changed(ModifierState::None, ModifierState::None);
         }
         self.next.notify_devices_changed(device_infos);
     }
@@ -146,6 +134,14 @@
     fn destroy(&mut self) {
         self.next.destroy();
     }
+
+    fn dump(&mut self, dump_str: String) -> String {
+        let mut result = "Sticky Keys filter: \n".to_string();
+        result += &format!("\tmodifier_state = {:?}\n", self.modifier_state);
+        result += &format!("\tlocked_modifier_state = {:?}\n", self.locked_modifier_state);
+        result += &format!("\tcontributing_devices = {:?}\n", self.contributing_devices);
+        self.next.dump(dump_str + &result)
+    }
 }
 
 fn is_modifier_key(keycode: i32) -> bool {
@@ -181,51 +177,53 @@
     )
 }
 
-fn get_ephemeral_modifier_key_mask(keycode: i32) -> u32 {
+fn get_ephemeral_modifier_key_mask(keycode: i32) -> ModifierState {
     match keycode {
-        KEYCODE_ALT_LEFT => META_ALT_LEFT_ON | META_ALT_ON,
-        KEYCODE_ALT_RIGHT => META_ALT_RIGHT_ON | META_ALT_ON,
-        KEYCODE_SHIFT_LEFT => META_SHIFT_LEFT_ON | META_SHIFT_ON,
-        KEYCODE_SHIFT_RIGHT => META_SHIFT_RIGHT_ON | META_SHIFT_ON,
-        KEYCODE_CTRL_LEFT => META_CTRL_LEFT_ON | META_CTRL_ON,
-        KEYCODE_CTRL_RIGHT => META_CTRL_RIGHT_ON | META_CTRL_ON,
-        KEYCODE_META_LEFT => META_META_LEFT_ON | META_META_ON,
-        KEYCODE_META_RIGHT => META_META_RIGHT_ON | META_META_ON,
-        _ => 0,
+        KEYCODE_ALT_LEFT => ModifierState::AltLeftOn | ModifierState::AltOn,
+        KEYCODE_ALT_RIGHT => ModifierState::AltRightOn | ModifierState::AltOn,
+        KEYCODE_SHIFT_LEFT => ModifierState::ShiftLeftOn | ModifierState::ShiftOn,
+        KEYCODE_SHIFT_RIGHT => ModifierState::ShiftRightOn | ModifierState::ShiftOn,
+        KEYCODE_CTRL_LEFT => ModifierState::CtrlLeftOn | ModifierState::CtrlOn,
+        KEYCODE_CTRL_RIGHT => ModifierState::CtrlRightOn | ModifierState::CtrlOn,
+        KEYCODE_META_LEFT => ModifierState::MetaLeftOn | ModifierState::MetaOn,
+        KEYCODE_META_RIGHT => ModifierState::MetaRightOn | ModifierState::MetaOn,
+        _ => ModifierState::None,
     }
 }
 
 /// Modifier mask including both left and right versions of a modifier key.
-fn get_symmetrical_modifier_key_mask(keycode: i32) -> u32 {
+fn get_symmetrical_modifier_key_mask(keycode: i32) -> ModifierState {
     match keycode {
-        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => META_ALT_LEFT_ON | META_ALT_RIGHT_ON | META_ALT_ON,
+        KEYCODE_ALT_LEFT | KEYCODE_ALT_RIGHT => {
+            ModifierState::AltLeftOn | ModifierState::AltRightOn | ModifierState::AltOn
+        }
         KEYCODE_SHIFT_LEFT | KEYCODE_SHIFT_RIGHT => {
-            META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON | META_SHIFT_ON
+            ModifierState::ShiftLeftOn | ModifierState::ShiftRightOn | ModifierState::ShiftOn
         }
         KEYCODE_CTRL_LEFT | KEYCODE_CTRL_RIGHT => {
-            META_CTRL_LEFT_ON | META_CTRL_RIGHT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlRightOn | ModifierState::CtrlOn
         }
         KEYCODE_META_LEFT | KEYCODE_META_RIGHT => {
-            META_META_LEFT_ON | META_META_RIGHT_ON | META_META_ON
+            ModifierState::MetaLeftOn | ModifierState::MetaRightOn | ModifierState::MetaOn
         }
-        _ => 0,
+        _ => ModifierState::None,
     }
 }
 
-fn clear_ephemeral_modifier_state(modifier_state: u32) -> u32 {
+fn clear_ephemeral_modifier_state(modifier_state: ModifierState) -> ModifierState {
     modifier_state
-        & !(META_ALT_LEFT_ON
-            | META_ALT_RIGHT_ON
-            | META_ALT_ON
-            | META_SHIFT_LEFT_ON
-            | META_SHIFT_RIGHT_ON
-            | META_SHIFT_ON
-            | META_CTRL_LEFT_ON
-            | META_CTRL_RIGHT_ON
-            | META_CTRL_ON
-            | META_META_LEFT_ON
-            | META_META_RIGHT_ON
-            | META_META_ON)
+        & !(ModifierState::AltLeftOn
+            | ModifierState::AltRightOn
+            | ModifierState::AltOn
+            | ModifierState::ShiftLeftOn
+            | ModifierState::ShiftRightOn
+            | ModifierState::ShiftOn
+            | ModifierState::CtrlLeftOn
+            | ModifierState::CtrlRightOn
+            | ModifierState::CtrlOn
+            | ModifierState::MetaLeftOn
+            | ModifierState::MetaRightOn
+            | ModifierState::MetaOn)
 }
 
 #[cfg(test)]
@@ -237,9 +235,7 @@
         StickyKeysFilter, KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT, KEYCODE_CAPS_LOCK,
         KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT, KEYCODE_FUNCTION, KEYCODE_META_LEFT,
         KEYCODE_META_RIGHT, KEYCODE_NUM_LOCK, KEYCODE_SCROLL_LOCK, KEYCODE_SHIFT_LEFT,
-        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM, META_ALT_LEFT_ON, META_ALT_ON, META_ALT_RIGHT_ON,
-        META_CTRL_LEFT_ON, META_CTRL_ON, META_CTRL_RIGHT_ON, META_META_LEFT_ON, META_META_ON,
-        META_META_RIGHT_ON, META_SHIFT_LEFT_ON, META_SHIFT_ON, META_SHIFT_RIGHT_ON,
+        KEYCODE_SHIFT_RIGHT, KEYCODE_SYM,
     };
     use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
     use binder::Strong;
@@ -247,6 +243,8 @@
         DeviceInfo::DeviceInfo, IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
         KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
     };
+    use input::KeyboardType;
+    use input::ModifierState;
     use std::sync::{Arc, RwLock};
 
     static DEVICE_ID: i32 = 1;
@@ -347,30 +345,30 @@
             Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
         );
         let test_states = &[
-            (KEYCODE_ALT_LEFT, META_ALT_ON | META_ALT_LEFT_ON),
-            (KEYCODE_ALT_RIGHT, META_ALT_ON | META_ALT_RIGHT_ON),
-            (KEYCODE_CTRL_LEFT, META_CTRL_ON | META_CTRL_LEFT_ON),
-            (KEYCODE_CTRL_RIGHT, META_CTRL_ON | META_CTRL_RIGHT_ON),
-            (KEYCODE_SHIFT_LEFT, META_SHIFT_ON | META_SHIFT_LEFT_ON),
-            (KEYCODE_SHIFT_RIGHT, META_SHIFT_ON | META_SHIFT_RIGHT_ON),
-            (KEYCODE_META_LEFT, META_META_ON | META_META_LEFT_ON),
-            (KEYCODE_META_RIGHT, META_META_ON | META_META_RIGHT_ON),
+            (KEYCODE_ALT_LEFT, ModifierState::AltOn | ModifierState::AltLeftOn),
+            (KEYCODE_ALT_RIGHT, ModifierState::AltOn | ModifierState::AltRightOn),
+            (KEYCODE_CTRL_LEFT, ModifierState::CtrlOn | ModifierState::CtrlLeftOn),
+            (KEYCODE_CTRL_RIGHT, ModifierState::CtrlOn | ModifierState::CtrlRightOn),
+            (KEYCODE_SHIFT_LEFT, ModifierState::ShiftOn | ModifierState::ShiftLeftOn),
+            (KEYCODE_SHIFT_RIGHT, ModifierState::ShiftOn | ModifierState::ShiftRightOn),
+            (KEYCODE_META_LEFT, ModifierState::MetaOn | ModifierState::MetaLeftOn),
+            (KEYCODE_META_RIGHT, ModifierState::MetaOn | ModifierState::MetaRightOn),
         ];
         for test_state in test_states.iter() {
             test_filter.clear();
             test_callbacks.clear();
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
-            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             // Re-send keys to lock it
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_DOWN });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
             assert_eq!(test_callbacks.get_last_modifier_state(), test_state.1);
@@ -382,8 +380,8 @@
             assert_eq!(test_callbacks.get_last_locked_modifier_state(), test_state.1);
 
             sticky_keys_filter.notify_key(&KeyEvent { keyCode: test_state.0, ..BASE_KEY_UP });
-            assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-            assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+            assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+            assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
         }
     }
 
@@ -398,14 +396,17 @@
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
     }
 
     #[test]
@@ -427,20 +428,26 @@
 
         assert_eq!(
             test_callbacks.get_last_modifier_state(),
-            META_SHIFT_LEFT_ON | META_SHIFT_ON | META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::ShiftLeftOn
+                | ModifierState::ShiftOn
+                | ModifierState::CtrlLeftOn
+                | ModifierState::CtrlOn
         );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
 
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
     }
 
@@ -458,13 +465,13 @@
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_DOWN });
         assert_eq!(
             test_filter.last_event().unwrap().metaState as u32,
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits()
         );
 
         sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEY_A, ..BASE_KEY_UP });
         assert_eq!(
             test_filter.last_event().unwrap().metaState as u32,
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            (ModifierState::CtrlLeftOn | ModifierState::CtrlOn).bits()
         );
     }
 
@@ -498,16 +505,23 @@
             ..BASE_KEY_UP
         });
 
-        sticky_keys_filter.notify_devices_changed(&[DeviceInfo { deviceId: 2, external: true }]);
-        assert_eq!(test_callbacks.get_last_modifier_state(), META_CTRL_LEFT_ON | META_CTRL_ON);
+        sticky_keys_filter.notify_devices_changed(&[DeviceInfo {
+            deviceId: 2,
+            external: true,
+            keyboardType: KeyboardType::Alphabetic as i32,
+        }]);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
         assert_eq!(
             test_callbacks.get_last_locked_modifier_state(),
-            META_CTRL_LEFT_ON | META_CTRL_ON
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
         );
 
         sticky_keys_filter.notify_devices_changed(&[]);
-        assert_eq!(test_callbacks.get_last_modifier_state(), 0);
-        assert_eq!(test_callbacks.get_last_locked_modifier_state(), 0);
+        assert_eq!(test_callbacks.get_last_modifier_state(), ModifierState::None);
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
     }
 
     fn setup_filter(
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 2415e42..744cf4a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -22,6 +22,15 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+// Source files shared with InputDispatcher's benchmarks and fuzzers
+filegroup {
+    name: "inputdispatcher_common_test_sources",
+    srcs: [
+        "FakeInputDispatcherPolicy.cpp",
+        "FakeWindows.cpp",
+    ],
+}
+
 cc_test {
     name: "inputflinger_tests",
     host_supported: true,
@@ -38,6 +47,7 @@
         "libinputflinger_defaults",
     ],
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "AnrTracker_test.cpp",
         "CapturedTouchpadEventConverter_test.cpp",
         "CursorInputMapper_test.cpp",
@@ -57,18 +67,24 @@
         "InputProcessorConverter_test.cpp",
         "InputDispatcher_test.cpp",
         "InputReader_test.cpp",
+        "InputTraceSession.cpp",
+        "InputTracingTest.cpp",
         "InstrumentedInputReader.cpp",
+        "JoystickInputMapper_test.cpp",
         "LatencyTracker_test.cpp",
         "MultiTouchMotionAccumulator_test.cpp",
         "NotifyArgs_test.cpp",
         "PointerChoreographer_test.cpp",
         "PreferStylusOverTouch_test.cpp",
         "PropertyProvider_test.cpp",
+        "RotaryEncoderInputMapper_test.cpp",
         "SlopController_test.cpp",
+        "SwitchInputMapper_test.cpp",
         "SyncQueue_test.cpp",
         "TimerProvider_test.cpp",
         "TestInputListener.cpp",
         "TouchpadInputMapper_test.cpp",
+        "VibratorInputMapper_test.cpp",
         "MultiTouchInputMapper_test.cpp",
         "KeyboardInputMapper_test.cpp",
         "UinputDevice.cpp",
@@ -103,5 +119,9 @@
     test_options: {
         unit_test: true,
     },
-    test_suites: ["device-tests"],
+    test_suites: [
+        "device-tests",
+        "device-platinum-tests",
+    ],
+    native_coverage: false,
 }
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
index b738abf..353011a 100644
--- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -20,6 +20,7 @@
 #include <memory>
 
 #include <EventHub.h>
+#include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
 #include <linux/input.h>
@@ -32,9 +33,14 @@
 #include "TestEventMatchers.h"
 #include "TestInputListener.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 using testing::AllOf;
+using testing::Each;
+using testing::ElementsAre;
+using testing::VariantWith;
 
 class CapturedTouchpadEventConverterTest : public testing::Test {
 public:
@@ -44,6 +50,8 @@
             mReader(mFakeEventHub, mFakePolicy, mFakeListener),
             mDevice(newDevice()),
             mDeviceContext(*mDevice, EVENTHUB_ID) {
+        input_flags::include_relative_axis_values_for_captured_touchpads(true);
+
         const size_t slotCount = 8;
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
         mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
@@ -123,7 +131,7 @@
 
 TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) {
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 2000, 0, 0, 40);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25);
@@ -147,8 +155,8 @@
     const InputDeviceInfo::MotionRange* posY =
             info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, posY);
-    EXPECT_NEAR(0, posY->min, EPSILON);
-    EXPECT_NEAR(2500, posY->max, EPSILON);
+    EXPECT_NEAR(-500, posY->min, EPSILON);
+    EXPECT_NEAR(2000, posY->max, EPSILON);
     EXPECT_NEAR(40, posY->resolution, EPSILON);
 
     const InputDeviceInfo::MotionRange* touchMajor =
@@ -179,8 +187,22 @@
     EXPECT_NEAR(800, toolMinor->max, EPSILON);
     EXPECT_NEAR(20, toolMinor->resolution, EPSILON);
 
-    // ...except orientation and pressure, which get scaled, and size, which is generated from other
-    // values.
+    // ...except for the relative motion axes, derived from the corresponding absolute ones:
+    const InputDeviceInfo::MotionRange* relX =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relX);
+    EXPECT_NEAR(-4000, relX->min, EPSILON);
+    EXPECT_NEAR(4000, relX->max, EPSILON);
+    EXPECT_NEAR(45, relX->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* relY =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relY);
+    EXPECT_NEAR(-2500, relY->min, EPSILON);
+    EXPECT_NEAR(2500, relY->max, EPSILON);
+    EXPECT_NEAR(40, relY->resolution, EPSILON);
+
+    // ...orientation and pressure, which get scaled:
     const InputDeviceInfo::MotionRange* orientation =
             info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, orientation);
@@ -195,6 +217,7 @@
     EXPECT_NEAR(1, pressure->max, EPSILON);
     EXPECT_NEAR(0, pressure->resolution, EPSILON);
 
+    // ... and size, which is generated from other values.
     const InputDeviceInfo::MotionRange* size =
             info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, size);
@@ -216,7 +239,9 @@
     // present, since it's generated from axes that aren't provided by this device).
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD));
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD));
-    EXPECT_EQ(2u, info.getMotionRanges().size());
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_EQ(4u, info.getMotionRanges().size());
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) {
@@ -232,28 +257,31 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+                      WithCoords(52, 99), WithRelativeMotion(2, -1),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
 
     std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(52, 99), WithRelativeMotion(0, 0), WithPointerCount(1u),
+                              WithToolType(ToolType::FINGER)))));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) {
@@ -504,13 +532,13 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(51, 100)));
+                      WithCoords(51, 100), WithRelativeMotion(0, 0)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 100)));
+                      WithCoords(52, 100), WithRelativeMotion(1, 0)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) {
@@ -550,7 +578,7 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(98, 148)));
+                      WithCoords(98, 148), WithRelativeMotion(-2, -2)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) {
@@ -572,17 +600,17 @@
     processAxis(conv, EV_KEY, BTN_TOUCH, 1);
     processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithToolType(ToolType::FINGER)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerToolType(0, ToolType::FINGER),
-                      WithPointerToolType(1, ToolType::FINGER)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithPointerCount(1u), WithToolType(ToolType::FINGER))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCount(2u),
+                                          WithPointerToolType(0, ToolType::FINGER),
+                                          WithPointerToolType(1, ToolType::FINGER)))));
 
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 51);
@@ -591,15 +619,16 @@
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251);
     processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_PALM);
 
-    args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithFlags(AMOTION_EVENT_FLAG_CANCELED), WithPointerCount(2u)));
+    std::list<NotifyArgs> args = processSync(conv);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithFlags(AMOTION_EVENT_FLAG_CANCELED)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithPointerCount(2u))));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerAndPalmTurningIntoFinger_reported) {
@@ -632,15 +661,15 @@
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 251);
     processAxis(conv, EV_ABS, ABS_MT_TOOL_TYPE, MT_TOOL_FINGER);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerCount(1u))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCount(2u)))));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) {
@@ -656,7 +685,8 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
@@ -670,18 +700,22 @@
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
     processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerCoords(0, 52, 99),
-                      WithPointerCoords(1, 250, 200), WithPointerToolType(0, ToolType::FINGER),
-                      WithPointerToolType(1, ToolType::FINGER)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerCount(1u), WithCoords(52, 99),
+                                          WithRelativeMotion(2, -1),
+                                          WithToolType(ToolType::FINGER))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                                          WithPointerRelativeMotion(0, 0, 0),
+                                          WithPointerCoords(1, 250, 200),
+                                          WithPointerRelativeMotion(1, 0, 0),
+                                          WithPointerToolType(0, ToolType::FINGER),
+                                          WithPointerToolType(1, ToolType::FINGER)))));
 
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
@@ -692,34 +726,96 @@
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
     processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
 
-    args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
-                      WithPointerCoords(0, 52, 99), WithPointerCoords(1, 255, 202),
-                      WithPointerToolType(1, ToolType::FINGER),
-                      WithPointerToolType(0, ToolType::FINGER)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerCoords(0, 52, 99),
-                      WithPointerCoords(1, 255, 202), WithPointerToolType(0, ToolType::FINGER),
-                      WithPointerToolType(1, ToolType::FINGER)));
+    std::list<NotifyArgs> args = processSync(conv);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerRelativeMotion(1, 5, 2))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerRelativeMotion(1, 0, 0)))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                              WithPointerRelativeMotion(0, 0, 0), WithPointerCoords(1, 255, 202),
+                              WithPointerToolType(1, ToolType::FINGER),
+                              WithPointerToolType(0, ToolType::FINGER)))));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
 
     args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithPointerCount(1u), WithCoords(255, 202),
+                                                         WithRelativeMotion(0, 0),
+                                                         WithToolType(ToolType::FINGER)))));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, RelativeMotionAxesClearedForNewFingerInSlot) {
+    CapturedTouchpadEventConverter conv = createConverter();
+    // Put down one finger.
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(50, 100), WithRelativeMotion(0, 0)));
+
+    // Move it in negative X and Y directions.
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 47);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 97);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(47, 97),
+                      WithRelativeMotion(-3, -3)));
+
+    // Lift it.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(47, 97),
+                                                         WithRelativeMotion(0, 0),
+                                                         WithPointerCount(1u)))));
+
+    // Put down another finger using the same slot. Relative axis values should be cleared.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 60);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 60);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(60, 60), WithRelativeMotion(0, 0)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 64);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 58);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(255, 202), WithToolType(ToolType::FINGER)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
-                      WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+                      WithCoords(64, 58), WithRelativeMotion(4, -2)));
 }
 
 // Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out.
@@ -737,17 +833,18 @@
     processAxis(conv, EV_KEY, BTN_TOUCH, 1);
     processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithPointerId(/*index=*/0, /*id=*/0)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
-                      WithPointerId(/*index=*/1, /*id=*/1)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithPointerCount(1u),
+                                          WithPointerId(/*index=*/0, /*id=*/0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerCount(2u),
+                                          WithPointerId(/*index=*/0, /*id=*/0),
+                                          WithPointerId(/*index=*/1, /*id=*/1)))));
 
     // Lift the finger in slot 0, freeing up pointer ID 0...
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
@@ -758,27 +855,30 @@
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 3);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 30);
 
-    args = processSync(conv);
-    ASSERT_EQ(3u, args.size());
+    std::list<NotifyArgs> args = processSync(conv);
     // Slot 1 being present will result in a MOVE event, even though it hasn't actually moved (see
     // comments in CapturedTouchpadEventConverter::sync).
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
-                      WithPointerId(/*index=*/0, /*id=*/0), WithPointerId(/*index=*/1, /*id=*/1)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
-                                       0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
-                      WithPointerId(/*index=*/1, /*id=*/1)));
-    args.pop_front();
-    // Slot 0 being lifted causes the finger from slot 1 to move up to index 0, but keep its
-    // previous ID. The new finger in slot 2 should take ID 0, which was just freed up.
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/1),
-                      WithPointerId(/*index=*/1, /*id=*/0)));
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerId(/*index=*/0, /*id=*/0),
+                                          WithPointerId(/*index=*/1, /*id=*/1))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerId(/*index=*/0, /*id=*/0),
+                                          WithPointerId(/*index=*/1, /*id=*/1))),
+                            // Slot 0 being lifted causes the finger from slot 1 to move up to index
+                            // 0, but keep its previous ID. The new finger in slot 2 should take ID
+                            // 0, which was just freed up.
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerId(/*index=*/0, /*id=*/1),
+                                          WithPointerId(/*index=*/1, /*id=*/0)))));
+    EXPECT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithPointerCount(2u))));
 }
 
 // Motion events without any pointers are invalid, so when a button press is reported in the same
@@ -797,33 +897,30 @@
 
     processAxis(conv, EV_KEY, BTN_LEFT, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
-                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithPointerCount(1u), WithCoords(50, 100),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
 
     processAxis(conv, EV_KEY, BTN_LEFT, 0);
-    args = processSync(conv);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
-                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(0)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithPointerCount(1u), WithCoords(50, 100),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
 }
 
 // Some touchpads sometimes report a button press before they report the finger touching the pad. In
@@ -841,15 +938,14 @@
     processAxis(conv, EV_KEY, BTN_TOUCH, 1);
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS), WithPointerCount(1u),
-                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithPointerCount(1u), WithCoords(50, 100),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
 }
 
 // When all fingers are lifted from a touchpad, we should release any buttons that are down, since
@@ -866,29 +962,25 @@
 
     processAxis(conv, EV_KEY, BTN_LEFT, 1);
 
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS))));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
     processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
-    args = processSync(conv);
-    ASSERT_EQ(3u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE), WithPointerCount(1u),
-                      WithCoords(50, 100), WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(0)));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_UP));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithPointerCount(1u), WithCoords(50, 100),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(0))),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
 
     processAxis(conv, EV_KEY, BTN_LEFT, 0);
     ASSERT_EQ(0u, processSync(conv).size());
@@ -908,48 +1000,41 @@
                 WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
 
     processAxis(conv, EV_KEY, BTN_LEFT, 1);
-    std::list<NotifyArgs> args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
 
     processAxis(conv, EV_KEY, BTN_RIGHT, 1);
-    args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                      AMOTION_EVENT_BUTTON_SECONDARY)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+                                                          AMOTION_EVENT_BUTTON_SECONDARY)))));
 
     processAxis(conv, EV_KEY, BTN_LEFT, 0);
-    args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                      WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY)))));
 
     processAxis(conv, EV_KEY, BTN_RIGHT, 0);
-    args = processSync(conv);
-    ASSERT_EQ(2u, args.size());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
-    args.pop_front();
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                      WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0)));
+    EXPECT_THAT(processSync(conv),
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+                                          WithButtonState(0)))));
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/CursorInputMapper_test.cpp b/services/inputflinger/tests/CursorInputMapper_test.cpp
index de74067..b27d02d 100644
--- a/services/inputflinger/tests/CursorInputMapper_test.cpp
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -17,11 +17,13 @@
 #include "CursorInputMapper.h"
 
 #include <list>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <variant>
 
 #include <android-base/logging.h>
+#include <android_companion_virtualdevice_flags.h>
 #include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
@@ -29,7 +31,6 @@
 #include <linux/input.h>
 #include <utils/Timers.h>
 
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InputReaderBase.h"
 #include "InterfaceMocks.h"
@@ -51,8 +52,8 @@
 constexpr auto BUTTON_RELEASE = AMOTION_EVENT_ACTION_BUTTON_RELEASE;
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
-constexpr int32_t DISPLAY_ID = 0;
-constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
 
@@ -93,41 +94,10 @@
     return v;
 }
 
-/**
- * A fake InputDeviceContext that allows the associated viewport to be specified for the mapper.
- *
- * This is currently necessary because InputMapperUnitTest doesn't register the mappers it creates
- * with the InputDevice object, meaning that InputDevice::isIgnored becomes true, and the input
- * device doesn't set its associated viewport when it's configured.
- *
- * TODO(b/319217713): work out a way to avoid this fake.
- */
-class ViewportFakingInputDeviceContext : public InputDeviceContext {
-public:
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
-                                     std::optional<DisplayViewport> viewport)
-          : InputDeviceContext(device, eventHubId), mAssociatedViewport(viewport) {}
-
-    ViewportFakingInputDeviceContext(InputDevice& device, int32_t eventHubId,
-                                     ui::Rotation orientation)
-          : ViewportFakingInputDeviceContext(device, eventHubId,
-                                             createPrimaryViewport(orientation)) {}
-
-    std::optional<DisplayViewport> getAssociatedViewport() const override {
-        return mAssociatedViewport;
-    }
-
-    void setViewport(const std::optional<DisplayViewport>& viewport) {
-        mAssociatedViewport = viewport;
-    }
-
-private:
-    std::optional<DisplayViewport> mAssociatedViewport;
-};
-
 } // namespace
 
 namespace input_flags = com::android::input::flags;
+namespace vd_flags = android::companion::virtualdevice::flags;
 
 /**
  * Unit tests for CursorInputMapper.
@@ -152,21 +122,21 @@
                 .WillRepeatedly(Return(false));
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
                 .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
 
         mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
         mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
     }
 
-    virtual bool isPointerChoreographerEnabled() { return false; }
-
     void createMapper() {
-        createDevice();
-        mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration,
-                                                       isPointerChoreographerEnabled());
+        mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 
     void setPointerCapture(bool enabled) {
-        mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+        mReaderConfiguration.pointerCaptureRequest.window = enabled ? sp<BBinder>::make() : nullptr;
         mReaderConfiguration.pointerCaptureRequest.seq = 1;
         int32_t generation = mDevice->getGeneration();
         std::list<NotifyArgs> args =
@@ -198,10 +168,9 @@
 protected:
     void SetUp() override {
         input_flags::enable_new_mouse_pointer_ballistics(false);
+        vd_flags::high_resolution_scroll(false);
         CursorInputMapperUnitTestBase::SetUp();
     }
-
-    bool isPointerChoreographerEnabled() override { return false; }
 };
 
 TEST_F(CursorInputMapperUnitTest, GetSourcesReturnsMouseInPointerMode) {
@@ -325,12 +294,9 @@
 
     // Disable pointer capture. Afterwards, events should be generated the usual way.
     setPointerCapture(false);
-    const auto expectedCoords = CursorInputMapperUnitTest::isPointerChoreographerEnabled()
-            ? WithCoords(0, 0)
-            : WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
-    const auto expectedCursorPosition = CursorInputMapperUnitTest::isPointerChoreographerEnabled()
-            ? WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION)
-            : WithCursorPosition(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+    const auto expectedCoords = WithCoords(0, 0);
+    const auto expectedCursorPosition =
+            WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION);
     args.clear();
     args += process(EV_REL, REL_X, 10);
     args += process(EV_REL, REL_Y, 20);
@@ -342,42 +308,6 @@
                               WithRelativeMotion(10.0f, 20.0f)))));
 }
 
-TEST_F(CursorInputMapperUnitTest,
-       PopulateDeviceInfoReturnsRangeFromPointerControllerInPointerMode) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    mFakePolicy->clearViewports();
-    mFakePointerController->clearBounds();
-    createMapper();
-
-    InputDeviceInfo info;
-    mMapper->populateDeviceInfo(info);
-
-    // Initially there should not be a valid motion range because there's no viewport or pointer
-    // bounds.
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
-    ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-
-    // When the bounds are set, then there should be a valid motion range.
-    mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
-    mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
-    std::list<NotifyArgs> args =
-            mMapper->reconfigure(systemTime(), mReaderConfiguration,
-                                 InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_THAT(args, testing::IsEmpty());
-
-    InputDeviceInfo info2;
-    mMapper->populateDeviceInfo(info2);
-
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 1,
-                                              800 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 2,
-                                              480 - 1, 0.0f, 0.0f));
-    ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
-                                              AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
-}
-
 TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsScaledRangeInNavigationMode) {
     mPropertyMap.addProperty("cursor.mode", "navigation");
     createMapper();
@@ -580,9 +510,9 @@
     // need to be rotated.
     mPropertyMap.addProperty("cursor.mode", "navigation");
     mPropertyMap.addProperty("cursor.orientationAware", "1");
-    createDevice();
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation90);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
@@ -598,9 +528,9 @@
     // Since InputReader works in the un-rotated coordinate space, only devices that are not
     // orientation-aware are affected by display rotation.
     mPropertyMap.addProperty("cursor.mode", "navigation");
-    createDevice();
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, ui::Rotation::Rotation0);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation0)));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0,  1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 1,  1,  1,  1));
@@ -611,7 +541,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0, -1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1,  1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation90));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation90)));
     std::list<NotifyArgs> args =
             mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
@@ -624,7 +555,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  0, -1));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1, -1, -1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation180));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation180)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  0, -1));
@@ -636,7 +568,8 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  0,  1,  0));
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1, -1));
 
-    deviceContext.setViewport(createPrimaryViewport(ui::Rotation::Rotation270));
+    EXPECT_CALL((*mDevice), getAssociatedViewport)
+            .WillRepeatedly(Return(createPrimaryViewport(ui::Rotation::Rotation270)));
     args = mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                 InputReaderConfiguration::Change::DISPLAY_INFO);
     ASSERT_NO_FATAL_FAILURE(testMotionRotation( 0,  1,  1,  0));
@@ -649,334 +582,9 @@
     ASSERT_NO_FATAL_FAILURE(testMotionRotation(-1,  1,  1,  1));
 }
 
-TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesOrientationChanges) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    DisplayViewport viewport = createPrimaryViewport(ui::Rotation::Rotation90);
-    mFakePointerController->setDisplayViewport(viewport);
-    mReaderConfiguration.setDisplayViewports({viewport});
-    createMapper();
-
-    // Verify that the coordinates are rotated.
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
-                              WithRelativeMotion(-20.0f, 10.0f)))));
-
-    // Enable Pointer Capture.
-    setPointerCapture(true);
-
-    // Move and verify rotation is not applied.
-    args = process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(ACTION_MOVE),
-                              WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                              WithCoords(10.0f, 20.0f)))));
-}
-
-TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) {
-    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
-    DisplayViewport secondaryViewport = createSecondaryViewport();
-    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
-    // Set up the secondary display as the display on which the pointer should be shown. The
-    // InputDevice is not associated with any display.
-    mFakePointerController->setDisplayViewport(secondaryViewport);
-    mFakePointerController->setPosition(100, 200);
-    createMapper();
-
-    // Ensure input events are generated for the secondary display.
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
-                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) {
-    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
-    DisplayViewport secondaryViewport = createSecondaryViewport();
-    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
-    // Set up the secondary display as the display on which the pointer should be shown.
-    mFakePointerController->setDisplayViewport(secondaryViewport);
-    mFakePointerController->setPosition(100, 200);
-    createDevice();
-    // Associate the InputDevice with the secondary display.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<
-            CursorInputMapper>(deviceContext, mReaderConfiguration,
-                               CursorInputMapperUnitTest::isPointerChoreographerEnabled());
-
-    // Ensure input events are generated for the secondary display.
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
-                              WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(110.0f, 220.0f)))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdIgnoresEventsForMismatchedPointerDisplay) {
-    DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
-    DisplayViewport secondaryViewport = createSecondaryViewport();
-    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
-    // Set up the primary display as the display on which the pointer should be shown.
-    mFakePointerController->setDisplayViewport(primaryViewport);
-    createDevice();
-    // Associate the InputDevice with the secondary display.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<
-            CursorInputMapper>(deviceContext, mReaderConfiguration,
-                               CursorInputMapperUnitTest::isPointerChoreographerEnabled());
-
-    // The mapper should not generate any events because it is associated with a display that is
-    // different from the pointer display.
-    std::list<NotifyArgs> args;
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args, testing::IsEmpty());
-}
-
-TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtons) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    std::list<NotifyArgs> args;
-
-    // press BTN_LEFT, release BTN_LEFT
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 1);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
-    args.clear();
-
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_LEFT, 0);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f)))));
-    args.clear();
-
-    // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 1);
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 1);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
-                                                          AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY |
-                                                          AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
-    args.clear();
-
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_RIGHT, 0);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_TERTIARY),
-                                          WithCoords(100.0f, 200.0f), WithPressure(1.0f)))));
-    args.clear();
-
-    args += process(ARBITRARY_TIME, EV_KEY, BTN_MIDDLE, 0);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithButtonState(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithButtonState(0),
-                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
-}
-
-class CursorInputMapperButtonKeyTest
-      : public CursorInputMapperUnitTest,
-        public testing::WithParamInterface<
-                std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
-                           int32_t /*expectedKeyCode*/>> {
-    virtual bool isPointerChoreographerEnabled() override { return false; }
-};
-
-TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKey) {
-    auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    std::list<NotifyArgs> args;
-
-    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
-                                                             WithKeyCode(expectedKeyCode))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithButtonState(expectedButtonState),
-                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithButtonState(expectedButtonState),
-                                          WithCoords(100.0f, 200.0f), WithPressure(0.0f)))));
-    args.clear();
-
-    args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 0);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithButtonState(0), WithCoords(100.0f, 200.0f),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyKeyArgs>(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
-                                                             WithKeyCode(expectedKeyCode)))));
-}
-
-INSTANTIATE_TEST_SUITE_P(
-        SideExtraBackAndForward, CursorInputMapperButtonKeyTest,
-        testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
-                        std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
-                        std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
-                        std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
-                                        AKEYCODE_FORWARD)));
-
-TEST_F(CursorInputMapperUnitTest, ProcessShouldMoveThePointerAroundInPointerMode) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
-    std::list<NotifyArgs> args;
-
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithCoords(110.0f, 220.0f), WithPressure(0.0f), WithSize(0.0f),
-                              WithTouchDimensions(0.0f, 0.0f), WithToolDimensions(0.0f, 0.0f),
-                              WithOrientation(0.0f), WithDistance(0.0f)))));
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
-}
-
-/**
- * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
- * pointer acceleration or speed processing should not be applied.
- */
-TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
-                                               /*highThreshold=*/100.f, /*acceleration=*/10.f);
-    mReaderConfiguration.pointerVelocityControlParameters = testParams;
-    mFakePolicy->setVelocityControlParams(testParams);
-    createMapper();
-
-    std::list<NotifyArgs> args;
-
-    // Move and verify scale is applied.
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithSource(AINPUT_SOURCE_MOUSE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE)))));
-    NotifyMotionArgs motionArgs = std::get<NotifyMotionArgs>(args.front());
-    const float relX = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const float relY = motionArgs.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    ASSERT_GT(relX, 10);
-    ASSERT_GT(relY, 20);
-    args.clear();
-
-    // Enable Pointer Capture
-    setPointerCapture(true);
-
-    // Move and verify scale is not applied.
-    args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
-    args += process(ARBITRARY_TIME, EV_REL, REL_Y, 20);
-    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
-                              WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(10, 20)))));
-}
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-class CursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_new_mouse_pointer_ballistics(false);
-        CursorInputMapperUnitTestBase::SetUp();
-    }
-
-    bool isPointerChoreographerEnabled() override { return true; }
-};
-
-TEST_F(CursorInputMapperUnitTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
+TEST_F(CursorInputMapperUnitTest, PopulateDeviceInfoReturnsRangeFromPolicy) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     mFakePolicy->clearViewports();
-    mFakePointerController->clearBounds();
     createMapper();
 
     InputDeviceInfo info;
@@ -1009,17 +617,14 @@
                                               AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
 }
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdWithAssociatedViewport) {
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
     DisplayViewport secondaryViewport = createSecondaryViewport();
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
     // Set up the secondary display as the display on which the pointer should be shown.
     // The InputDevice is not associated with any display.
-    mFakePointerController->setDisplayViewport(secondaryViewport);
-    mFakePointerController->setPosition(100, 200);
-    createDevice();
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
     // Ensure input events are generated for the secondary display.
@@ -1032,17 +637,15 @@
                               WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
 }
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer,
+TEST_F(CursorInputMapperUnitTest,
        ConfigureDisplayIdShouldGenerateEventForMismatchedPointerDisplay) {
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation90);
     DisplayViewport secondaryViewport = createSecondaryViewport();
     mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
     // Set up the primary display as the display on which the pointer should be shown.
-    mFakePointerController->setDisplayViewport(primaryViewport);
-    createDevice();
     // Associate the InputDevice with the secondary display.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, secondaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     // With PointerChoreographer enabled, there could be a PointerController for the associated
     // display even if it is different from the pointer display. So the mapper should generate an
@@ -1057,13 +660,10 @@
                               WithDisplayId(SECONDARY_DISPLAY_ID), WithCoords(0.0f, 0.0f)))));
 }
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
+TEST_F(CursorInputMapperUnitTest, ProcessShouldHandleAllButtonsWithZeroCoords) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     createMapper();
 
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
     std::list<NotifyArgs> args;
 
     // press BTN_LEFT, release BTN_LEFT
@@ -1147,21 +747,17 @@
                                           WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
 }
 
-class CursorInputMapperButtonKeyTestWithChoreographer
-      : public CursorInputMapperUnitTestWithChoreographer,
+class CursorInputMapperButtonKeyTest
+      : public CursorInputMapperUnitTest,
         public testing::WithParamInterface<
                 std::tuple<int32_t /*evdevCode*/, int32_t /*expectedButtonState*/,
                            int32_t /*expectedKeyCode*/>> {};
 
-TEST_P(CursorInputMapperButtonKeyTestWithChoreographer,
-       ProcessShouldHandleButtonKeyWithZeroCoords) {
+TEST_P(CursorInputMapperButtonKeyTest, ProcessShouldHandleButtonKeyWithZeroCoords) {
     auto [evdevCode, expectedButtonState, expectedKeyCode] = GetParam();
     mPropertyMap.addProperty("cursor.mode", "pointer");
     createMapper();
 
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
     std::list<NotifyArgs> args;
 
     args += process(ARBITRARY_TIME, EV_KEY, evdevCode, 1);
@@ -1195,20 +791,17 @@
 }
 
 INSTANTIATE_TEST_SUITE_P(
-        SideExtraBackAndForward, CursorInputMapperButtonKeyTestWithChoreographer,
+        SideExtraBackAndForward, CursorInputMapperButtonKeyTest,
         testing::Values(std::make_tuple(BTN_SIDE, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
                         std::make_tuple(BTN_EXTRA, AMOTION_EVENT_BUTTON_FORWARD, AKEYCODE_FORWARD),
                         std::make_tuple(BTN_BACK, AMOTION_EVENT_BUTTON_BACK, AKEYCODE_BACK),
                         std::make_tuple(BTN_FORWARD, AMOTION_EVENT_BUTTON_FORWARD,
                                         AKEYCODE_FORWARD)));
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
+TEST_F(CursorInputMapperUnitTest, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     createMapper();
 
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(100, 200);
-
     std::list<NotifyArgs> args;
 
     args += process(ARBITRARY_TIME, EV_REL, REL_X, 10);
@@ -1223,7 +816,77 @@
                               WithOrientation(0.0f), WithDistance(0.0f)))));
 }
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
+TEST_F(CursorInputMapperUnitTest, ProcessRegularScroll) {
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(1.0f, 1.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, ProcessHighResScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
+TEST_F(CursorInputMapperUnitTest, HighResScrollIgnoresRegularScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
+/**
+ * When Pointer Capture is enabled, we expect to report unprocessed relative movements, so any
+ * pointer acceleration or speed processing should not be applied.
+ */
+TEST_F(CursorInputMapperUnitTest, PointerCaptureDisablesVelocityProcessing) {
     mPropertyMap.addProperty("cursor.mode", "pointer");
     const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
                                                /*highThreshold=*/100.f, /*acceleration=*/10.f);
@@ -1267,7 +930,7 @@
     ASSERT_EQ(20, relY2);
 }
 
-TEST_F(CursorInputMapperUnitTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
+TEST_F(CursorInputMapperUnitTest, ConfigureDisplayIdNoAssociatedViewport) {
     // Set up the default display.
     mFakePolicy->clearViewports();
     mFakePolicy->addDisplayViewport(createPrimaryViewport(ui::Rotation::Rotation0));
@@ -1279,9 +942,6 @@
 
     createMapper();
 
-    mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    mFakePointerController->setPosition(100, 200);
-
     // Ensure input events are generated without display ID or coords, because they will be decided
     // later by PointerChoreographer.
     std::list<NotifyArgs> args;
@@ -1291,7 +951,8 @@
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
+                              WithSource(AINPUT_SOURCE_MOUSE),
+                              WithDisplayId(ui::LogicalDisplayId::INVALID),
                               WithCoords(0.0f, 0.0f)))));
 }
 
@@ -1302,8 +963,6 @@
         input_flags::enable_new_mouse_pointer_ballistics(true);
         CursorInputMapperUnitTestBase::SetUp();
     }
-
-    bool isPointerChoreographerEnabled() override { return true; }
 };
 
 TEST_F(CursorInputMapperUnitTestWithNewBallistics, PointerCaptureDisablesVelocityProcessing) {
@@ -1342,9 +1001,8 @@
     mPropertyMap.addProperty("cursor.mode", "pointer");
     DisplayViewport primaryViewport = createPrimaryViewport(ui::Rotation::Rotation0);
     mReaderConfiguration.setDisplayViewports({primaryViewport});
-    createDevice();
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID, primaryViewport);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(primaryViewport));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
 
@@ -1380,12 +1038,10 @@
     mReaderConfiguration.setDisplayViewports({primaryViewport});
     // Disable acceleration for the display.
     mReaderConfiguration.displaysWithMousePointerAccelerationDisabled.emplace(DISPLAY_ID);
-    createDevice();
 
     // Don't associate the device with the display yet.
-    ViewportFakingInputDeviceContext deviceContext(*mDevice, EVENTHUB_ID,
-                                                   /*viewport=*/std::nullopt);
-    mMapper = createInputMapper<CursorInputMapper>(deviceContext, mReaderConfiguration);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(std::nullopt));
+    mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
 
     std::list<NotifyArgs> args;
 
@@ -1399,7 +1055,7 @@
     ASSERT_GT(coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y), 20.f);
 
     // Now associate the device with the display, and verify that acceleration is disabled.
-    deviceContext.setViewport(primaryViewport);
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(primaryViewport));
     args += mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
     args.clear();
@@ -1413,6 +1069,72 @@
                               WithRelativeMotion(10, 20)))));
 }
 
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessRegularScroll) {
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(1.0f, 1.0f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, ProcessHighResScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
+TEST_F(CursorInputMapperUnitTestWithNewBallistics, HighResScrollIgnoresRegularScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    createMapper();
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_REL, REL_HWHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithSource(AINPUT_SOURCE_MOUSE),
+                                          WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                                          WithScroll(0.5f, 0.5f)))));
+}
+
 namespace {
 
 // Minimum timestamp separation between subsequent input events from a Bluetooth device.
@@ -1422,14 +1144,11 @@
 
 } // namespace
 
+// --- BluetoothCursorInputMapperUnitTest ---
+
 class BluetoothCursorInputMapperUnitTest : public CursorInputMapperUnitTestBase {
 protected:
-    void SetUp() override {
-        SetUpWithBus(BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
+    void SetUp() override { SetUpWithBus(BUS_BLUETOOTH); }
 };
 
 TEST_F(BluetoothCursorInputMapperUnitTest, TimestampSmoothening) {
@@ -1537,123 +1256,4 @@
     argsList.clear();
 }
 
-// --- BluetoothCursorInputMapperUnitTestWithChoreographer ---
-
-class BluetoothCursorInputMapperUnitTestWithChoreographer : public CursorInputMapperUnitTestBase {
-protected:
-    void SetUp() override {
-        SetUpWithBus(BUS_BLUETOOTH);
-
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
-    }
-
-    bool isPointerChoreographerEnabled() override { return true; }
-};
-
-TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmoothening) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-    std::list<NotifyArgs> argsList;
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
-    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(argsList,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
-    argsList.clear();
-
-    // Process several events that come in quick succession, according to their timestamps.
-    for (int i = 0; i < 3; i++) {
-        constexpr static nsecs_t delta = ms2ns(1);
-        static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
-        kernelEventTime += delta;
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        argsList += process(kernelEventTime, EV_REL, REL_X, 1);
-        argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
-        EXPECT_THAT(argsList,
-                    ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                  WithEventTime(expectedEventTime)))));
-        argsList.clear();
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningIsCapped) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-    std::list<NotifyArgs> argsList;
-
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
-    argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(argsList,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
-    argsList.clear();
-
-    // Process several events with the same timestamp from the kernel.
-    // Ensure that we do not generate events too far into the future.
-    constexpr static int32_t numEvents =
-            MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
-    for (int i = 0; i < numEvents; i++) {
-        expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
-
-        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
-        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-        EXPECT_THAT(argsList,
-                    ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                  WithEventTime(expectedEventTime)))));
-        argsList.clear();
-    }
-
-    // By processing more events with the same timestamp, we should not generate events with a
-    // timestamp that is more than the specified max time delta from the timestamp at its injection.
-    const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
-    for (int i = 0; i < 3; i++) {
-        argsList += process(ARBITRARY_TIME, EV_REL, REL_X, 1);
-        argsList += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
-        EXPECT_THAT(argsList,
-                    ElementsAre(VariantWith<NotifyMotionArgs>(
-                            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                  WithEventTime(cappedEventTime)))));
-        argsList.clear();
-    }
-}
-
-TEST_F(BluetoothCursorInputMapperUnitTestWithChoreographer, TimestampSmootheningNotUsed) {
-    mPropertyMap.addProperty("cursor.mode", "pointer");
-    createMapper();
-    std::list<NotifyArgs> argsList;
-
-    nsecs_t kernelEventTime = ARBITRARY_TIME;
-    nsecs_t expectedEventTime = ARBITRARY_TIME;
-    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
-    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(argsList,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
-    argsList.clear();
-
-    // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
-    // smoothening is not needed, its timestamp is not affected.
-    kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
-    expectedEventTime = kernelEventTime;
-
-    argsList += process(kernelEventTime, EV_REL, REL_X, 1);
-    argsList += process(kernelEventTime, EV_SYN, SYN_REPORT, 0);
-    EXPECT_THAT(argsList,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithEventTime(expectedEventTime)))));
-    argsList.clear();
-}
-
 } // namespace android
diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp
index 2e296da..0e3d15a 100644
--- a/services/inputflinger/tests/EventHub_test.cpp
+++ b/services/inputflinger/tests/EventHub_test.cpp
@@ -48,9 +48,6 @@
                 case EventHubInterface::DEVICE_REMOVED:
                     ALOGI("Device removed: %i", event.deviceId);
                     break;
-                case EventHubInterface::FINISHED_DEVICE_SCAN:
-                    ALOGI("Finished device scan.");
-                    break;
             }
         } else {
             ALOGI("Device %" PRId32 " : time = %" PRId64 ", type %i, code %i, value %i",
@@ -145,15 +142,13 @@
     // None of the existing system devices should be changing while this test is run.
     // Check that the returned device ids are unique for all of the existing devices.
     EXPECT_EQ(existingDevices.size(), events.size() - 1);
-    // The last event should be "finished device scan"
-    EXPECT_EQ(EventHubInterface::FINISHED_DEVICE_SCAN, events[events.size() - 1].type);
 }
 
 int32_t EventHubTest::waitForDeviceCreation() {
     // Wait a little longer than usual, to ensure input device has time to be created
     std::vector<RawEvent> events = getEvents(2);
-    if (events.size() != 2) {
-        ADD_FAILURE() << "Instead of 2 events, received " << events.size();
+    if (events.size() != 1) {
+        ADD_FAILURE() << "Instead of 1 event, received " << events.size();
         return 0; // this value is unused
     }
     const RawEvent& deviceAddedEvent = events[0];
@@ -161,21 +156,15 @@
     InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceAddedEvent.deviceId);
     const int32_t deviceId = deviceAddedEvent.deviceId;
     EXPECT_EQ(identifier.name, mKeyboard->getName());
-    const RawEvent& finishedDeviceScanEvent = events[1];
-    EXPECT_EQ(static_cast<int32_t>(EventHubInterface::FINISHED_DEVICE_SCAN),
-              finishedDeviceScanEvent.type);
     return deviceId;
 }
 
 void EventHubTest::waitForDeviceClose(int32_t deviceId) {
     std::vector<RawEvent> events = getEvents(2);
-    ASSERT_EQ(2U, events.size());
+    ASSERT_EQ(1U, events.size());
     const RawEvent& deviceRemovedEvent = events[0];
     EXPECT_EQ(static_cast<int32_t>(EventHubInterface::DEVICE_REMOVED), deviceRemovedEvent.type);
     EXPECT_EQ(deviceId, deviceRemovedEvent.deviceId);
-    const RawEvent& finishedDeviceScanEvent = events[1];
-    EXPECT_EQ(static_cast<int32_t>(EventHubInterface::FINISHED_DEVICE_SCAN),
-              finishedDeviceScanEvent.type);
 }
 
 void EventHubTest::assertNoMoreEvents() {
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index daa000f..943de6e 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -16,6 +16,8 @@
 
 #include "FakeEventHub.h"
 
+#include <optional>
+
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
@@ -86,10 +88,6 @@
     return device->disable();
 }
 
-void FakeEventHub::finishDeviceScan() {
-    enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
-}
-
 void FakeEventHub::addConfigurationProperty(int32_t deviceId, const char* key, const char* value) {
     getDevice(deviceId)->configuration.addProperty(key, value);
 }
@@ -103,7 +101,6 @@
     Device* device = getDevice(deviceId);
 
     RawAbsoluteAxisInfo info;
-    info.valid = true;
     info.minValue = minValue;
     info.maxValue = maxValue;
     info.flat = flat;
@@ -154,9 +151,10 @@
     getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
 }
 
-void FakeEventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
+void FakeEventHub::setKeyRemapping(int32_t deviceId,
+                                   const std::map<int32_t, int32_t>& keyRemapping) const {
     Device* device = getDevice(deviceId);
-    device->keyRemapping.insert_or_assign(fromKeyCode, toKeyCode);
+    device->keyRemapping = keyRemapping;
 }
 
 void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) {
@@ -263,18 +261,16 @@
     return device->configuration;
 }
 
-status_t FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                           RawAbsoluteAxisInfo* outAxisInfo) const {
+std::optional<RawAbsoluteAxisInfo> FakeEventHub::getAbsoluteAxisInfo(int32_t deviceId,
+                                                                     int axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxes.indexOfKey(axis);
         if (index >= 0) {
-            *outAxisInfo = device->absoluteAxes.valueAt(index);
-            return OK;
+            return device->absoluteAxes.valueAt(index);
         }
     }
-    outAxisInfo->clear();
-    return -1;
+    return std::nullopt;
 }
 
 bool FakeEventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -417,18 +413,15 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-status_t FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                            int32_t* outValue) const {
+std::optional<int32_t> FakeEventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const {
     Device* device = getDevice(deviceId);
     if (device) {
         ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
         if (index >= 0) {
-            *outValue = device->absoluteAxisValue.valueAt(index);
-            return OK;
+            return device->absoluteAxisValue.valueAt(index);
         }
     }
-    *outValue = 0;
-    return -1;
+    return std::nullopt;
 }
 
 void FakeEventHub::setMtSlotValues(int32_t deviceId, int32_t axis,
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index f07b344..2dfbb23 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -55,7 +55,7 @@
         KeyedVector<int32_t, int32_t> absoluteAxisValue;
         KeyedVector<int32_t, KeyInfo> keysByScanCode;
         KeyedVector<int32_t, KeyInfo> keysByUsageCode;
-        std::unordered_map<int32_t, int32_t> keyRemapping;
+        std::map<int32_t, int32_t> keyRemapping;
         KeyedVector<int32_t, bool> leds;
         // fake mapping which would normally come from keyCharacterMap
         std::unordered_map<int32_t, int32_t> keyCodeMapping;
@@ -112,8 +112,6 @@
     status_t enableDevice(int32_t deviceId) override;
     status_t disableDevice(int32_t deviceId) override;
 
-    void finishDeviceScan();
-
     void addConfigurationProperty(int32_t deviceId, const char* key, const char* value);
     void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration);
 
@@ -131,7 +129,7 @@
     void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode,
                 uint32_t flags);
     void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode);
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const;
+    void setKeyRemapping(int32_t deviceId, const std::map<int32_t, int32_t>& keyRemapping) const;
     void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition);
 
     void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
@@ -168,8 +166,8 @@
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
     int32_t getDeviceControllerNumber(int32_t) const override;
     std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override;
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override;
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override;
     bool hasRelativeAxis(int32_t deviceId, int axis) const override;
     bool hasInputProperty(int32_t, int) const override;
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
@@ -187,7 +185,7 @@
     std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override;
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     // Return true if the device has non-empty key layout.
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
new file mode 100644
index 0000000..db68d8a
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -0,0 +1,488 @@
+/*
+ * Copyright 2024 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 "FakeInputDispatcherPolicy.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- FakeInputDispatcherPolicy ---
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
+    assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
+        ASSERT_EQ(event.getType(), InputEventType::KEY);
+        EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+        const auto& keyEvent = static_cast<const KeyEvent&>(event);
+        EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
+        EXPECT_EQ(keyEvent.getAction(), args.action);
+    });
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalled(const NotifyMotionArgs& args,
+                                                                vec2 point) {
+    assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
+        ASSERT_EQ(event.getType(), InputEventType::MOTION);
+        EXPECT_EQ(event.getDisplayId(), args.displayId);
+
+        const auto& motionEvent = static_cast<const MotionEvent&>(event);
+        EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
+        EXPECT_EQ(motionEvent.getAction(), args.action);
+        EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+        EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+    });
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_EQ(nullptr, mFilteredEvent);
+}
+
+void FakeInputDispatcherPolicy::assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mLastNotifySwitch);
+    // We do not check id because it is not exposed to the policy
+    EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
+    EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
+    EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
+    EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
+    mLastNotifySwitch = std::nullopt;
+}
+
+void FakeInputDispatcherPolicy::assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
+    std::scoped_lock lock(mLock);
+    ASSERT_EQ(touchedToken, mOnPointerDownToken);
+    mOnPointerDownToken.clear();
+}
+
+void FakeInputDispatcherPolicy::assertOnPointerDownWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mOnPointerDownToken == nullptr)
+            << "Expected onPointerDownOutsideFocus to not have been called";
+}
+
+void FakeInputDispatcherPolicy::assertNotifyNoFocusedWindowAnrWasCalled(
+        std::chrono::nanoseconds timeout,
+        const std::shared_ptr<InputApplicationHandle>& expectedApplication) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    std::shared_ptr<InputApplicationHandle> application;
+    ASSERT_NO_FATAL_FAILURE(
+            application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
+    ASSERT_EQ(expectedApplication, application);
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled(
+        std::chrono::nanoseconds timeout, const sp<gui::WindowInfoHandle>& window) {
+    LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null");
+    assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(),
+                                            window->getInfo()->ownerPid);
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowUnresponsiveWasCalled(
+        std::chrono::nanoseconds timeout, const sp<IBinder>& expectedToken,
+        std::optional<gui::Pid> expectedPid) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result;
+    ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
+    ASSERT_EQ(expectedToken, result.token);
+    ASSERT_EQ(expectedPid, result.pid);
+}
+
+sp<IBinder> FakeInputDispatcherPolicy::getUnresponsiveWindowToken(
+        std::chrono::nanoseconds timeout) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock);
+    const auto& [token, _] = result;
+    return token;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyWindowResponsiveWasCalled(
+        const sp<IBinder>& expectedToken, std::optional<gui::Pid> expectedPid) {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result;
+    ASSERT_NO_FATAL_FAILURE(result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
+    ASSERT_EQ(expectedToken, result.token);
+    ASSERT_EQ(expectedPid, result.pid);
+}
+
+sp<IBinder> FakeInputDispatcherPolicy::getResponsiveWindowToken() {
+    std::unique_lock lock(mLock);
+    android::base::ScopedLockAssertion assumeLocked(mLock);
+    AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock);
+    const auto& [token, _] = result;
+    return token;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyAnrWasNotCalled() {
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mAnrApplications.empty());
+    ASSERT_TRUE(mAnrWindows.empty());
+    ASSERT_TRUE(mResponsiveWindows.empty())
+            << "ANR was not called, but please also consume the 'connection is responsive' "
+               "signal";
+}
+
+PointerCaptureRequest FakeInputDispatcherPolicy::assertSetPointerCaptureCalled(
+        const sp<gui::WindowInfoHandle>& window, bool enabled) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (!mPointerCaptureChangedCondition
+                 .wait_for(lock, 100ms, [this, enabled, window]() REQUIRES(mLock) {
+                     if (enabled) {
+                         return mPointerCaptureRequest->isEnable() &&
+                                 mPointerCaptureRequest->window == window->getToken();
+                     } else {
+                         return !mPointerCaptureRequest->isEnable();
+                     }
+                 })) {
+        ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << window->getName() << ", "
+                      << enabled << ") to be called.";
+        return {};
+    }
+    auto request = *mPointerCaptureRequest;
+    mPointerCaptureRequest.reset();
+    return request;
+}
+
+void FakeInputDispatcherPolicy::assertSetPointerCaptureNotCalled() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
+        FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
+                  "enabled = "
+               << std::to_string(mPointerCaptureRequest->isEnable());
+    }
+    mPointerCaptureRequest.reset();
+}
+
+void FakeInputDispatcherPolicy::assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
+                                                       const sp<IBinder>& targetToken) {
+    dispatcher.waitForIdle();
+    std::scoped_lock lock(mLock);
+    ASSERT_TRUE(mNotifyDropWindowWasCalled);
+    ASSERT_EQ(targetToken, mDropTargetWindowToken);
+    mNotifyDropWindowWasCalled = false;
+}
+
+void FakeInputDispatcherPolicy::assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<sp<IBinder>> receivedToken =
+            getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
+                                                  mNotifyInputChannelBroken);
+    ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
+    ASSERT_EQ(token, *receivedToken);
+}
+
+void FakeInputDispatcherPolicy::setInterceptKeyTimeout(std::chrono::milliseconds timeout) {
+    mInterceptKeyTimeout = timeout;
+}
+
+std::chrono::nanoseconds FakeInputDispatcherPolicy::getKeyWaitingForEventsTimeout() {
+    return 500ms;
+}
+
+void FakeInputDispatcherPolicy::setStaleEventTimeout(std::chrono::nanoseconds timeout) {
+    mStaleEventTimeout = timeout;
+}
+
+void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching) {
+    mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching;
+}
+
+void FakeInputDispatcherPolicy::assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    if (!mFocusedDisplayNotifiedCondition.wait_for(lock, 100ms,
+                                                   [this, expectedDisplay]() REQUIRES(mLock) {
+                                                       if (!mNotifiedFocusedDisplay.has_value() ||
+                                                           mNotifiedFocusedDisplay.value() !=
+                                                                   expectedDisplay) {
+                                                           return false;
+                                                       }
+                                                       return true;
+                                                   })) {
+        ADD_FAILURE() << "Timed out waiting for notifyFocusedDisplayChanged(" << expectedDisplay
+                      << ") to be called.";
+    }
+}
+
+void FakeInputDispatcherPolicy::assertUserActivityNotPoked() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    std::optional<UserActivityPokeEvent> pokeEvent =
+            getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                  mNotifyUserActivity);
+
+    ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked";
+}
+
+void FakeInputDispatcherPolicy::assertUserActivityPoked(
+        std::optional<UserActivityPokeEvent> expectedPokeEvent) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    std::optional<UserActivityPokeEvent> pokeEvent =
+            getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
+                                                  mNotifyUserActivity);
+    ASSERT_TRUE(pokeEvent) << "Expected a user poke event";
+
+    if (expectedPokeEvent) {
+        ASSERT_EQ(expectedPokeEvent, *pokeEvent);
+    }
+}
+
+void FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasCalled(int32_t deviceId,
+                                                                       std::set<gui::Uid> uids) {
+    ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
+}
+
+void FakeInputDispatcherPolicy::assertNotifyDeviceInteractionWasNotCalled() {
+    ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
+}
+
+void FakeInputDispatcherPolicy::setUnhandledKeyHandler(
+        std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
+    std::scoped_lock lock(mLock);
+    mUnhandledKeyHandler = handler;
+}
+
+void FakeInputDispatcherPolicy::assertUnhandledKeyReported(int32_t keycode) {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<int32_t> unhandledKeycode =
+            getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
+                                                  mNotifyUnhandledKey);
+    ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
+    ASSERT_EQ(unhandledKeycode, keycode);
+}
+
+void FakeInputDispatcherPolicy::assertUnhandledKeyNotReported() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+    std::optional<int32_t> unhandledKeycode =
+            getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
+                                                  mNotifyUnhandledKey);
+    ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
+}
+
+template <class T>
+T FakeInputDispatcherPolicy::getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout,
+                                                            std::queue<T>& storage,
+                                                            std::unique_lock<std::mutex>& lock)
+        REQUIRES(mLock) {
+    // If there is an ANR, Dispatcher won't be idle because there are still events
+    // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
+    // before checking if ANR was called.
+    // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
+    // to provide it some time to act. 100ms seems reasonable.
+    std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
+    const std::chrono::time_point start = std::chrono::steady_clock::now();
+    std::optional<T> token =
+            getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr);
+    if (!token.has_value()) {
+        ADD_FAILURE() << "Did not receive the ANR callback";
+        return {};
+    }
+
+    const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
+    // Ensure that the ANR didn't get raised too early. We can't be too strict here because
+    // the dispatcher started counting before this function was called
+    if (std::chrono::abs(timeout - waited) > 100ms) {
+        ADD_FAILURE() << "ANR was raised too early or too late. Expected "
+                      << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
+                      << "ms, but waited "
+                      << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
+                      << "ms instead";
+    }
+    return *token;
+}
+
+template <class T>
+std::optional<T> FakeInputDispatcherPolicy::getItemFromStorageLockedInterruptible(
+        std::chrono::nanoseconds timeout, std::queue<T>& storage,
+        std::unique_lock<std::mutex>& lock, std::condition_variable& condition) REQUIRES(mLock) {
+    condition.wait_for(lock, timeout, [&storage]() REQUIRES(mLock) { return !storage.empty(); });
+    if (storage.empty()) {
+        return std::nullopt;
+    }
+    T item = storage.front();
+    storage.pop();
+    return std::make_optional(item);
+}
+
+void FakeInputDispatcherPolicy::notifyWindowUnresponsive(const sp<IBinder>& connectionToken,
+                                                         std::optional<gui::Pid> pid,
+                                                         const std::string&) {
+    std::scoped_lock lock(mLock);
+    mAnrWindows.push({connectionToken, pid});
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyWindowResponsive(const sp<IBinder>& connectionToken,
+                                                       std::optional<gui::Pid> pid) {
+    std::scoped_lock lock(mLock);
+    mResponsiveWindows.push({connectionToken, pid});
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyNoFocusedWindowAnr(
+        const std::shared_ptr<InputApplicationHandle>& applicationHandle) {
+    std::scoped_lock lock(mLock);
+    mAnrApplications.push(applicationHandle);
+    mNotifyAnr.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyInputChannelBroken(const sp<IBinder>& connectionToken) {
+    std::scoped_lock lock(mLock);
+    mBrokenInputChannels.push(connectionToken);
+    mNotifyInputChannelBroken.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) {}
+
+void FakeInputDispatcherPolicy::notifySensorEvent(int32_t deviceId,
+                                                  InputDeviceSensorType sensorType,
+                                                  InputDeviceSensorAccuracy accuracy,
+                                                  nsecs_t timestamp,
+                                                  const std::vector<float>& values) {}
+
+void FakeInputDispatcherPolicy::notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
+                                                     InputDeviceSensorAccuracy accuracy) {}
+
+void FakeInputDispatcherPolicy::notifyVibratorState(int32_t deviceId, bool isOn) {}
+
+bool FakeInputDispatcherPolicy::filterInputEvent(const InputEvent& inputEvent,
+                                                 uint32_t policyFlags) {
+    std::scoped_lock lock(mLock);
+    switch (inputEvent.getType()) {
+        case InputEventType::KEY: {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
+            mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
+            break;
+        }
+
+        case InputEventType::MOTION: {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
+            mFilteredEvent = std::make_unique<MotionEvent>(motionEvent);
+            break;
+        }
+        default: {
+            ADD_FAILURE() << "Should only filter keys or motions";
+            break;
+        }
+    }
+    return true;
+}
+
+void FakeInputDispatcherPolicy::interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) {
+    if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) {
+        // Clear intercept state when we handled the event.
+        mInterceptKeyTimeout = 0ms;
+    }
+}
+
+void FakeInputDispatcherPolicy::interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t,
+                                                              int32_t, nsecs_t, uint32_t&) {}
+
+nsecs_t FakeInputDispatcherPolicy::interceptKeyBeforeDispatching(const sp<IBinder>&,
+                                                                 const KeyEvent&, uint32_t) {
+    if (mConsumeKeyBeforeDispatching) {
+        return -1;
+    }
+    nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
+    // Clear intercept state so we could dispatch the event in next wake.
+    mInterceptKeyTimeout = 0ms;
+    return delay;
+}
+
+std::optional<KeyEvent> FakeInputDispatcherPolicy::dispatchUnhandledKey(const sp<IBinder>&,
+                                                                        const KeyEvent& event,
+                                                                        uint32_t) {
+    std::scoped_lock lock(mLock);
+    mReportedUnhandledKeycodes.emplace(event.getKeyCode());
+    mNotifyUnhandledKey.notify_all();
+    return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
+}
+
+void FakeInputDispatcherPolicy::notifySwitch(nsecs_t when, uint32_t switchValues,
+                                             uint32_t switchMask, uint32_t policyFlags) {
+    std::scoped_lock lock(mLock);
+    // We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
+    // essentially a passthrough for notifySwitch.
+    mLastNotifySwitch =
+            NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
+}
+
+void FakeInputDispatcherPolicy::pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                                                 ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mNotifyUserActivity.notify_all();
+    mUserActivityPokeEvents.push({eventTime, eventType, displayId});
+}
+
+bool FakeInputDispatcherPolicy::isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) {
+    return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
+}
+
+void FakeInputDispatcherPolicy::onPointerDownOutsideFocus(const sp<IBinder>& newToken) {
+    std::scoped_lock lock(mLock);
+    mOnPointerDownToken = newToken;
+}
+
+void FakeInputDispatcherPolicy::setPointerCapture(const PointerCaptureRequest& request) {
+    std::scoped_lock lock(mLock);
+    mPointerCaptureRequest = {request};
+    mPointerCaptureChangedCondition.notify_all();
+}
+
+void FakeInputDispatcherPolicy::notifyDropWindow(const sp<IBinder>& token, float x, float y) {
+    std::scoped_lock lock(mLock);
+    mNotifyDropWindowWasCalled = true;
+    mDropTargetWindowToken = token;
+}
+
+void FakeInputDispatcherPolicy::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                                        const std::set<gui::Uid>& uids) {
+    ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
+}
+
+void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal(
+        const std::function<void(const InputEvent&)>& verify) {
+    std::scoped_lock lock(mLock);
+    ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
+    verify(*mFilteredEvent);
+    mFilteredEvent = nullptr;
+}
+
+void FakeInputDispatcherPolicy::notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) {
+    std::scoped_lock lock(mLock);
+    mNotifiedFocusedDisplay = displayId;
+    mFocusedDisplayNotifiedCondition.notify_all();
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index fb2db06..a9e39d1 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -16,76 +16,196 @@
 
 #pragma once
 
-#include <android-base/logging.h>
 #include "InputDispatcherPolicyInterface.h"
 
-namespace android {
+#include "InputDispatcherInterface.h"
+#include "NotifyArgs.h"
 
-// --- FakeInputDispatcherPolicy ---
+#include <condition_variable>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/thread_annotations.h>
+#include <binder/IBinder.h>
+#include <gui/PidUid.h>
+#include <gui/WindowInfo.h>
+#include <input/BlockingQueue.h>
+#include <input/Input.h>
+
+namespace android {
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
 public:
     FakeInputDispatcherPolicy() = default;
     virtual ~FakeInputDispatcherPolicy() = default;
 
-private:
-    void notifyConfigurationChanged(nsecs_t) override {}
+    struct AnrResult {
+        sp<IBinder> token{};
+        std::optional<gui::Pid> pid{};
+    };
 
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
-    }
+    struct UserActivityPokeEvent {
+        nsecs_t eventTime;
+        int32_t eventType;
+        ui::LogicalDisplayId displayId;
+
+        bool operator==(const UserActivityPokeEvent& rhs) const = default;
+        inline friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) {
+            os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType
+               << ", displayId=" << ev.displayId << "]";
+            return os;
+        }
+    };
+
+    void assertFilterInputEventWasCalled(const NotifyKeyArgs& args);
+    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point);
+    void assertFilterInputEventWasNotCalled();
+    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args);
+    void assertOnPointerDownEquals(const sp<IBinder>& touchedToken);
+    void assertOnPointerDownWasNotCalled();
+    /**
+     * This function must be called soon after the expected ANR timer starts,
+     * because we are also checking how much time has passed.
+     */
+    void assertNotifyNoFocusedWindowAnrWasCalled(
+            std::chrono::nanoseconds timeout,
+            const std::shared_ptr<InputApplicationHandle>& expectedApplication);
+    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+                                                 const sp<gui::WindowInfoHandle>& window);
+    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
+                                                 const sp<IBinder>& expectedToken,
+                                                 std::optional<gui::Pid> expectedPid);
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
+    sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout);
+    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
+                                               std::optional<gui::Pid> expectedPid);
+    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
+    sp<IBinder> getResponsiveWindowToken();
+    void assertNotifyAnrWasNotCalled();
+    PointerCaptureRequest assertSetPointerCaptureCalled(const sp<gui::WindowInfoHandle>& window,
+                                                        bool enabled);
+    void assertSetPointerCaptureNotCalled();
+    void assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
+                                const sp<IBinder>& targetToken);
+    void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token);
+    /**
+     * Set policy timeout. A value of zero means next key will not be intercepted.
+     */
+    void setInterceptKeyTimeout(std::chrono::milliseconds timeout);
+    std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override;
+    void setStaleEventTimeout(std::chrono::nanoseconds timeout);
+    void assertUserActivityNotPoked();
+    /**
+     * Asserts that a user activity poke has happened. The earliest recorded poke event will be
+     * cleared after this call.
+     *
+     * If an expected UserActivityPokeEvent is provided, asserts that the given event is the
+     * earliest recorded poke event.
+     */
+    void assertUserActivityPoked(std::optional<UserActivityPokeEvent> expectedPokeEvent = {});
+    void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids);
+    void assertNotifyDeviceInteractionWasNotCalled();
+    void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler);
+    void assertUnhandledKeyReported(int32_t keycode);
+    void assertUnhandledKeyNotReported();
+    void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching);
+    void assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay);
+
+private:
+    std::mutex mLock;
+    std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
+    sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
+    std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
+
+    std::condition_variable mPointerCaptureChangedCondition;
+
+    std::optional<ui::LogicalDisplayId> mNotifiedFocusedDisplay GUARDED_BY(mLock);
+    std::condition_variable mFocusedDisplayNotifiedCondition;
+
+    std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
+    // ANR handling
+    std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
+    std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock);
+    std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock);
+    std::condition_variable mNotifyAnr;
+    std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock);
+    std::condition_variable mNotifyInputChannelBroken;
+
+    sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
+    bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
+
+    std::condition_variable mNotifyUserActivity;
+    std::queue<UserActivityPokeEvent> mUserActivityPokeEvents;
+
+    std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
+
+    std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
+
+    bool mConsumeKeyBeforeDispatching = false;
+
+    BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
+
+    std::condition_variable mNotifyUnhandledKey;
+    std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
+    std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+
+    /**
+     * All three ANR-related callbacks behave the same way, so we use this generic function to wait
+     * for a specific container to become non-empty. When the container is non-empty, return the
+     * first entry from the container and erase it.
+     */
+    template <class T>
+    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
+                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock);
+
+    template <class T>
+    std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout,
+                                                           std::queue<T>& storage,
+                                                           std::unique_lock<std::mutex>& lock,
+                                                           std::condition_variable& condition)
+            REQUIRES(mLock);
 
     void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string& reason) override {
-        LOG(ERROR) << "Window is not responding: " << reason;
-    }
-
+                                  const std::string&) override;
     void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {}
-
-    void notifyInputChannelBroken(const sp<IBinder>&) override {}
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
+                                std::optional<gui::Pid> pid) override;
+    void notifyNoFocusedWindowAnr(
+            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override;
+    void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override;
+    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override;
     void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
                            InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
+                           const std::vector<float>& values) override;
+    void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
+                              InputDeviceSensorAccuracy accuracy) override;
+    void notifyVibratorState(int32_t deviceId, bool isOn) override;
+    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override;
+    void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override;
+    void interceptMotionBeforeQueueing(ui::LogicalDisplayId, uint32_t, int32_t, nsecs_t,
+                                       uint32_t&) override;
+    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override;
+    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
+                                                 uint32_t) override;
+    void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
+                      uint32_t policyFlags) override;
+    void pokeUserActivity(nsecs_t eventTime, int32_t eventType,
+                          ui::LogicalDisplayId displayId) override;
+    bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override;
+    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override;
+    void setPointerCapture(const PointerCaptureRequest& request) override;
+    void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
+    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+                                 const std::set<gui::Uid>& uids) override;
+    void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) override;
 
-    void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        return true; // dispatch event normally
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
-
-    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        return 0;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
-                                                 uint32_t) override {
-        return {};
-    }
-
-    void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
-
-    void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
-
-    void setPointerCapture(const PointerCaptureRequest&) override {}
-
-    void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
-
-    void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {}
+    void assertFilterInputEventWasCalledInternal(
+            const std::function<void(const InputEvent&)>& verify);
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 9e93712..f373cac 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -16,6 +16,7 @@
 
 #include "FakeInputReaderPolicy.h"
 
+#include <android-base/properties.h>
 #include <android-base/thread_annotations.h>
 #include <gtest/gtest.h>
 
@@ -24,20 +25,30 @@
 
 namespace android {
 
+namespace {
+
+static const int HW_TIMEOUT_MULTIPLIER = base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
+} // namespace
+
 void FakeInputReaderPolicy::assertInputDevicesChanged() {
-    waitForInputDevices([](bool devicesChanged) {
-        if (!devicesChanged) {
-            FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
-        }
-    });
+    waitForInputDevices(
+            [](bool devicesChanged) {
+                if (!devicesChanged) {
+                    FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
+                }
+            },
+            ADD_INPUT_DEVICE_TIMEOUT);
 }
 
 void FakeInputReaderPolicy::assertInputDevicesNotChanged() {
-    waitForInputDevices([](bool devicesChanged) {
-        if (devicesChanged) {
-            FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
-        }
-    });
+    waitForInputDevices(
+            [](bool devicesChanged) {
+                if (devicesChanged) {
+                    FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
+                }
+            },
+            INPUT_DEVICES_DIDNT_CHANGE_TIMEOUT);
 }
 
 void FakeInputReaderPolicy::assertStylusGestureNotified(int32_t deviceId) {
@@ -58,6 +69,17 @@
     ASSERT_FALSE(mDeviceIdOfNotifiedStylusGesture);
 }
 
+void FakeInputReaderPolicy::assertTouchpadHardwareStateNotified() {
+    std::unique_lock lock(mLock);
+    base::ScopedLockAssertion assumeLocked(mLock);
+
+    const bool success =
+            mTouchpadHardwareStateNotified.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
+                return mTouchpadHardwareState.has_value();
+            });
+    ASSERT_TRUE(success) << "Timed out waiting for hardware state to be notified";
+}
+
 void FakeInputReaderPolicy::clearViewports() {
     mViewports.clear();
     mConfig.setDisplayViewports(mViewports);
@@ -82,9 +104,9 @@
     mConfig.setDisplayViewports(mViewports);
 }
 
-void FakeInputReaderPolicy::addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
-                                               ui::Rotation orientation, bool isActive,
-                                               const std::string& uniqueId,
+void FakeInputReaderPolicy::addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width,
+                                               int32_t height, ui::Rotation orientation,
+                                               bool isActive, const std::string& uniqueId,
                                                std::optional<uint8_t> physicalPort,
                                                ViewportType type) {
     const bool isRotated = orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270;
@@ -129,7 +151,7 @@
 
 void FakeInputReaderPolicy::addInputPortAssociation(const std::string& inputPort,
                                                     uint8_t displayPort) {
-    mConfig.portAssociations.insert({inputPort, displayPort});
+    mConfig.inputPortToDisplayPortAssociations.insert({inputPort, displayPort});
 }
 
 void FakeInputReaderPolicy::addDeviceTypeAssociation(const std::string& inputPort,
@@ -139,7 +161,7 @@
 
 void FakeInputReaderPolicy::addInputUniqueIdAssociation(const std::string& inputUniqueId,
                                                         const std::string& displayUniqueId) {
-    mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
+    mConfig.inputPortToDisplayUniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
 }
 
 void FakeInputReaderPolicy::addKeyboardLayoutAssociation(const std::string& inputUniqueId,
@@ -155,11 +177,6 @@
     mConfig.disabledDevices.erase(deviceId);
 }
 
-void FakeInputReaderPolicy::setPointerController(
-        std::shared_ptr<FakePointerController> controller) {
-    mPointerController = std::move(controller);
-}
-
 const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
     return mConfig;
 }
@@ -178,16 +195,12 @@
     transform = t;
 }
 
-PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(bool enabled) {
-    mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
+PointerCaptureRequest FakeInputReaderPolicy::setPointerCapture(const sp<IBinder>& window) {
+    mConfig.pointerCaptureRequest = {window, mNextPointerCaptureSequenceNumber++};
     return mConfig.pointerCaptureRequest;
 }
 
-void FakeInputReaderPolicy::setShowTouches(bool enabled) {
-    mConfig.showTouches = enabled;
-}
-
-void FakeInputReaderPolicy::setDefaultPointerDisplayId(int32_t pointerDisplayId) {
+void FakeInputReaderPolicy::setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId) {
     mConfig.defaultPointerDisplayId = pointerDisplayId;
 }
 
@@ -228,11 +241,6 @@
     *outConfig = mConfig;
 }
 
-std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
-        int32_t /*deviceId*/) {
-    return mPointerController;
-}
-
 void FakeInputReaderPolicy::notifyInputDevicesChanged(
         const std::vector<InputDeviceInfo>& inputDevices) {
     std::scoped_lock lock(mLock);
@@ -241,6 +249,17 @@
     mDevicesChangedCondition.notify_all();
 }
 
+void FakeInputReaderPolicy::notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                                        int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+    mTouchpadHardwareState = schs;
+    mTouchpadHardwareStateNotified.notify_all();
+}
+
+void FakeInputReaderPolicy::notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+}
+
 std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
     return nullptr;
@@ -250,14 +269,16 @@
     return "";
 }
 
-void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
+void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged,
+                                                std::chrono::milliseconds timeout) {
     std::unique_lock<std::mutex> lock(mLock);
     base::ScopedLockAssertion assumeLocked(mLock);
 
     const bool devicesChanged =
-            mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
-                return mInputDevicesChanged;
-            });
+            mDevicesChangedCondition.wait_for(lock, timeout * HW_TIMEOUT_MULTIPLIER,
+                                              [this]() REQUIRES(mLock) {
+                                                  return mInputDevicesChanged;
+                                              });
     ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
     mInputDevicesChanged = false;
 }
@@ -269,8 +290,8 @@
 }
 
 std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay(
-        int32_t associatedDisplayId) {
-    if (associatedDisplayId == ADISPLAY_ID_NONE) {
+        ui::LogicalDisplayId associatedDisplayId) {
+    if (!associatedDisplayId.isValid()) {
         associatedDisplayId = mConfig.defaultPointerDisplayId;
     }
     for (auto& viewport : mViewports) {
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index da5085d..3a2b4e9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -26,7 +26,6 @@
 #include <InputDevice.h>
 #include <InputReaderBase.h>
 
-#include "FakePointerController.h"
 #include "input/DisplayViewport.h"
 #include "input/InputDevice.h"
 
@@ -43,13 +42,14 @@
     void assertInputDevicesNotChanged();
     void assertStylusGestureNotified(int32_t deviceId);
     void assertStylusGestureNotNotified();
+    void assertTouchpadHardwareStateNotified();
 
     virtual void clearViewports();
     std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
     std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
     std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const;
     void addDisplayViewport(DisplayViewport viewport);
-    void addDisplayViewport(int32_t displayId, int32_t width, int32_t height,
+    void addDisplayViewport(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                             ui::Rotation orientation, bool isActive, const std::string& uniqueId,
                             std::optional<uint8_t> physicalPort, ViewportType type);
     bool updateViewport(const DisplayViewport& viewport);
@@ -62,15 +62,13 @@
                                       const KeyboardLayoutInfo& layoutInfo);
     void addDisabledDevice(int32_t deviceId);
     void removeDisabledDevice(int32_t deviceId);
-    void setPointerController(std::shared_ptr<FakePointerController> controller);
     const InputReaderConfiguration& getReaderConfiguration() const;
     const std::vector<InputDeviceInfo> getInputDevices() const;
     TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
                                                            ui::Rotation surfaceRotation);
     void setTouchAffineTransformation(const TouchAffineTransformation t);
-    PointerCaptureRequest setPointerCapture(bool enabled);
-    void setShowTouches(bool enabled);
-    void setDefaultPointerDisplayId(int32_t pointerDisplayId);
+    PointerCaptureRequest setPointerCapture(const sp<IBinder>& window);
+    void setDefaultPointerDisplayId(ui::LogicalDisplayId pointerDisplayId);
     void setPointerGestureEnabled(bool enabled);
     float getPointerGestureMovementSpeedRatio();
     float getPointerGestureZoomSpeedRatio();
@@ -80,24 +78,25 @@
     void setIsInputMethodConnectionActive(bool active);
     bool isInputMethodConnectionActive() override;
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override;
+            ui::LogicalDisplayId associatedDisplayId) override;
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(
-            int32_t /*deviceId*/) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                     int32_t deviceId) override;
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
     std::string getDeviceAlias(const InputDeviceIdentifier&) override;
-    void waitForInputDevices(std::function<void(bool)> processDevicesChanged);
+    void waitForInputDevices(std::function<void(bool)> processDevicesChanged,
+                             std::chrono::milliseconds timeout);
     void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
 
     mutable std::mutex mLock;
     std::condition_variable mDevicesChangedCondition;
 
     InputReaderConfiguration mConfig;
-    std::shared_ptr<FakePointerController> mPointerController;
     std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
     bool mInputDevicesChanged GUARDED_BY(mLock){false};
     std::vector<DisplayViewport> mViewports;
@@ -107,6 +106,9 @@
     std::condition_variable mStylusGestureNotifiedCondition;
     std::optional<DeviceId> mDeviceIdOfNotifiedStylusGesture GUARDED_BY(mLock){};
 
+    std::condition_variable mTouchpadHardwareStateNotified;
+    std::optional<SelfContainedHardwareState> mTouchpadHardwareState GUARDED_BY(mLock){};
+
     uint32_t mNextPointerCaptureSequenceNumber{0};
 };
 
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
index 4655ee8..b46055e 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.cpp
+++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp
@@ -37,10 +37,9 @@
     return std::visit([](const auto& event) { return event.id; }, v);
 }
 
-MotionEvent toInputEvent(
-        const trace::TracedMotionEvent& e,
-        const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs,
-        const std::array<uint8_t, 32>& hmac) {
+MotionEvent toInputEvent(const trace::TracedMotionEvent& e,
+                         const trace::WindowDispatchArgs& dispatchArgs,
+                         const std::array<uint8_t, 32>& hmac) {
     MotionEvent traced;
     traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action, e.actionButton,
                       dispatchArgs.resolvedFlags, e.edgeFlags, e.metaState, e.buttonState,
@@ -51,13 +50,12 @@
     return traced;
 }
 
-KeyEvent toInputEvent(const trace::TracedKeyEvent& e,
-                      const trace::InputTracingBackendInterface::WindowDispatchArgs& dispatchArgs,
+KeyEvent toInputEvent(const trace::TracedKeyEvent& e, const trace::WindowDispatchArgs& dispatchArgs,
                       const std::array<uint8_t, 32>& hmac) {
     KeyEvent traced;
     traced.initialize(e.id, e.deviceId, e.source, e.displayId, hmac, e.action,
-                      dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState, e.repeatCount,
-                      e.downTime, e.eventTime);
+                      dispatchArgs.resolvedFlags, e.keyCode, e.scanCode, e.metaState,
+                      dispatchArgs.resolvedKeyRepeatCount, e.downTime, e.eventTime);
     return traced;
 }
 
@@ -120,7 +118,7 @@
 
     auto tracedDispatchesIt =
             std::find_if(mTracedWindowDispatches.begin(), mTracedWindowDispatches.end(),
-                         [&](const WindowDispatchArgs& args) {
+                         [&](const trace::WindowDispatchArgs& args) {
                              return args.windowId == expectedWindowId &&
                                      getId(args.eventEntry) == expectedEvent.getId();
                          });
@@ -163,7 +161,8 @@
 
 // --- FakeInputTracingBackend ---
 
-void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) {
+void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event,
+                                            const trace::TracedEventMetadata&) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedEvents.emplace(event.id, event);
@@ -171,7 +170,8 @@
     mTrace->mEventTracedCondition.notify_all();
 }
 
-void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) {
+void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event,
+                                               const trace::TracedEventMetadata&) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedEvents.emplace(event.id, event);
@@ -179,7 +179,8 @@
     mTrace->mEventTracedCondition.notify_all();
 }
 
-void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) {
+void FakeInputTracingBackend::traceWindowDispatch(const trace::WindowDispatchArgs& args,
+                                                  const trace::TracedEventMetadata&) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedWindowDispatches.push_back(args);
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h
index 1b3613d..cd4b507 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.h
+++ b/services/inputflinger/tests/FakeInputTracingBackend.h
@@ -59,8 +59,7 @@
     std::mutex mLock;
     std::condition_variable mEventTracedCondition;
     std::unordered_map<uint32_t /*eventId*/, trace::TracedEvent> mTracedEvents GUARDED_BY(mLock);
-    using WindowDispatchArgs = trace::InputTracingBackendInterface::WindowDispatchArgs;
-    std::vector<WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock);
+    std::vector<trace::WindowDispatchArgs> mTracedWindowDispatches GUARDED_BY(mLock);
     std::vector<std::pair<std::variant<KeyEvent, MotionEvent>, int32_t /*windowId*/>>
             mExpectedEvents GUARDED_BY(mLock);
 
@@ -83,9 +82,12 @@
 private:
     std::shared_ptr<VerifyingTrace> mTrace;
 
-    void traceKeyEvent(const trace::TracedKeyEvent& entry) override;
-    void traceMotionEvent(const trace::TracedMotionEvent& entry) override;
-    void traceWindowDispatch(const WindowDispatchArgs& entry) override;
+    void traceKeyEvent(const trace::TracedKeyEvent& entry,
+                       const trace::TracedEventMetadata&) override;
+    void traceMotionEvent(const trace::TracedMotionEvent& entry,
+                          const trace::TracedEventMetadata&) override;
+    void traceWindowDispatch(const trace::WindowDispatchArgs& entry,
+                             const trace::TracedEventMetadata&) override;
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index dc199e2..887a939 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -32,7 +32,7 @@
     mHaveBounds = false;
 }
 
-const std::map<int32_t, std::vector<int32_t>>& FakePointerController::getSpots() {
+const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& FakePointerController::getSpots() {
     return mSpotsByDisplay;
 }
 
@@ -51,9 +51,9 @@
     return {mX, mY};
 }
 
-int32_t FakePointerController::getDisplayId() const {
+ui::LogicalDisplayId FakePointerController::getDisplayId() const {
     if (!mEnabled || !mDisplayId) {
-        return ADISPLAY_ID_NONE;
+        return ui::LogicalDisplayId::INVALID;
     }
     return *mDisplayId;
 }
@@ -76,7 +76,17 @@
     mCustomIconStyle = icon.style;
 }
 
-void FakePointerController::assertViewportSet(int32_t displayId) {
+void FakePointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) {
+    mDisplaysToSkipScreenshotFlagChanged = true;
+    mDisplaysToSkipScreenshot.insert(displayId);
+}
+
+void FakePointerController::clearSkipScreenshotFlags() {
+    mDisplaysToSkipScreenshotFlagChanged = true;
+    mDisplaysToSkipScreenshot.clear();
+}
+
+void FakePointerController::assertViewportSet(ui::LogicalDisplayId displayId) {
     ASSERT_TRUE(mDisplayId);
     ASSERT_EQ(displayId, mDisplayId);
 }
@@ -91,7 +101,7 @@
     ASSERT_NEAR(y, actualY, 1);
 }
 
-void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+void FakePointerController::assertSpotCount(ui::LogicalDisplayId displayId, int32_t count) {
     auto it = mSpotsByDisplay.find(displayId);
     ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
     ASSERT_EQ(static_cast<size_t>(count), it->second.size());
@@ -117,14 +127,25 @@
     ASSERT_EQ(std::nullopt, mCustomIconStyle);
 }
 
-bool FakePointerController::isPointerShown() {
-    return mIsPointerShown;
+void FakePointerController::assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId) {
+    ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) != mDisplaysToSkipScreenshot.end());
 }
 
-std::optional<FloatRect> FakePointerController::getBounds() const {
-    if (!mEnabled) return std::nullopt;
+void FakePointerController::assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId) {
+    ASSERT_TRUE(mDisplaysToSkipScreenshot.find(displayId) == mDisplaysToSkipScreenshot.end());
+}
 
-    return mHaveBounds ? std::make_optional<FloatRect>(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt;
+void FakePointerController::assertSkipScreenshotFlagChanged() {
+    ASSERT_TRUE(mDisplaysToSkipScreenshotFlagChanged);
+    mDisplaysToSkipScreenshotFlagChanged = false;
+}
+
+void FakePointerController::assertSkipScreenshotFlagNotChanged() {
+    ASSERT_FALSE(mDisplaysToSkipScreenshotFlagChanged);
+}
+
+bool FakePointerController::isPointerShown() {
+    return mIsPointerShown;
 }
 
 void FakePointerController::move(float deltaX, float deltaY) {
@@ -150,7 +171,7 @@
 }
 
 void FakePointerController::setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
-                                     int32_t displayId) {
+                                     ui::LogicalDisplayId displayId) {
     if (!mEnabled) return;
 
     std::vector<int32_t> newSpots;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 536b447..9b773a7 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -17,10 +17,10 @@
 #pragma once
 
 #include <PointerControllerInterface.h>
-#include <gui/constants.h>
 #include <input/DisplayViewport.h>
 #include <input/Input.h>
 #include <utils/BitSet.h>
+#include <unordered_set>
 
 namespace android {
 
@@ -37,46 +37,53 @@
 
     void setBounds(float minX, float minY, float maxX, float maxY);
     void clearBounds();
-    const std::map<int32_t, std::vector<int32_t>>& getSpots();
+    const std::map<ui::LogicalDisplayId, std::vector<int32_t>>& getSpots();
 
     void setPosition(float x, float y) override;
     FloatPoint getPosition() const override;
-    int32_t getDisplayId() const override;
+    ui::LogicalDisplayId getDisplayId() const override;
     void setDisplayViewport(const DisplayViewport& viewport) override;
     void updatePointerIcon(PointerIconStyle iconId) override;
     void setCustomPointerIcon(const SpriteIcon& icon) override;
+    void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
+    void clearSkipScreenshotFlags() override;
     void fade(Transition) override;
 
-    void assertViewportSet(int32_t displayId);
+    void assertViewportSet(ui::LogicalDisplayId displayId);
     void assertViewportNotSet();
     void assertPosition(float x, float y);
-    void assertSpotCount(int32_t displayId, int32_t count);
+    void assertSpotCount(ui::LogicalDisplayId displayId, int32_t count);
     void assertPointerIconSet(PointerIconStyle iconId);
     void assertPointerIconNotSet();
     void assertCustomPointerIconSet(PointerIconStyle iconId);
     void assertCustomPointerIconNotSet();
+    void assertIsSkipScreenshotFlagSet(ui::LogicalDisplayId displayId);
+    void assertIsSkipScreenshotFlagNotSet(ui::LogicalDisplayId displayId);
+    void assertSkipScreenshotFlagChanged();
+    void assertSkipScreenshotFlagNotChanged();
     bool isPointerShown();
 
 private:
     std::string dump() override { return ""; }
-    std::optional<FloatRect> getBounds() const override;
     void move(float deltaX, float deltaY) override;
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
     void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
-                  int32_t displayId) override;
+                  ui::LogicalDisplayId displayId) override;
     void clearSpots() override;
 
     const bool mEnabled;
     bool mHaveBounds{false};
     float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
     float mX{0}, mY{0};
-    std::optional<int32_t> mDisplayId;
+    std::optional<ui::LogicalDisplayId> mDisplayId;
     bool mIsPointerShown{false};
     std::optional<PointerIconStyle> mIconStyle;
     std::optional<PointerIconStyle> mCustomIconStyle;
 
-    std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+    std::map<ui::LogicalDisplayId, std::vector<int32_t>> mSpotsByDisplay;
+    std::unordered_set<ui::LogicalDisplayId> mDisplaysToSkipScreenshot;
+    bool mDisplaysToSkipScreenshotFlagChanged{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
deleted file mode 100644
index fe25130..0000000
--- a/services/inputflinger/tests/FakeWindowHandle.h
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android-base/logging.h>
-#include "../dispatcher/InputDispatcher.h"
-
-using android::base::Result;
-using android::gui::Pid;
-using android::gui::TouchOcclusionMode;
-using android::gui::Uid;
-using android::gui::WindowInfo;
-using android::gui::WindowInfoHandle;
-
-namespace android {
-namespace inputdispatcher {
-
-namespace {
-
-// The default pid and uid for windows created by the test.
-constexpr gui::Pid WINDOW_PID{999};
-constexpr gui::Uid WINDOW_UID{1001};
-
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
-
-} // namespace
-
-class FakeInputReceiver {
-public:
-    std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) {
-        uint32_t consumeSeq = 0;
-        std::unique_ptr<InputEvent> event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t result = WOULD_BLOCK;
-        while (result == WOULD_BLOCK) {
-            InputEvent* rawEventPtr = nullptr;
-            result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                       &rawEventPtr);
-            event = std::unique_ptr<InputEvent>(rawEventPtr);
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > timeout) {
-                if (timeout != 0ms) {
-                    LOG(ERROR) << "Waited too long for consumer to produce an event, giving up";
-                }
-                break;
-            }
-        }
-        // Events produced by this factory are owned pointers.
-        if (result != OK) {
-            if (timeout == 0ms) {
-                // This is likely expected. No need to log.
-            } else {
-                LOG(ERROR) << "Received result =  " << result << " from consume";
-            }
-            return nullptr;
-        }
-        result = mConsumer.sendFinishedSignal(consumeSeq, true);
-        if (result != OK) {
-            LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal";
-        }
-        return event;
-    }
-
-    explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name)
-          : mConsumer(std::move(channel)) {}
-
-    virtual ~FakeInputReceiver() {}
-
-private:
-    std::unique_ptr<InputChannel> mClientChannel;
-    InputConsumer mConsumer;
-    DynamicInputEventFactory mEventFactory;
-};
-
-class FakeWindowHandle : public WindowInfoHandle {
-public:
-    static const int32_t WIDTH = 600;
-    static const int32_t HEIGHT = 800;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     InputDispatcher& dispatcher, const std::string name, int32_t displayId)
-          : mName(name) {
-        Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name);
-        mInfo.token = (*channel)->getConnectionToken();
-        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-
-        inputApplicationHandle->updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-
-        mInfo.id = sId++;
-        mInfo.name = name;
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.alpha = 1.0;
-        mInfo.frame.left = 0;
-        mInfo.frame.top = 0;
-        mInfo.frame.right = WIDTH;
-        mInfo.frame.bottom = HEIGHT;
-        mInfo.transform.set(0, 0);
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = displayId;
-        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
-    }
-
-    sp<FakeWindowHandle> clone(int32_t displayId) {
-        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
-        handle->mInfo = mInfo;
-        handle->mInfo.displayId = displayId;
-        handle->mInfo.id = sId++;
-        handle->mInputReceiver = mInputReceiver;
-        return handle;
-    }
-
-    void setTouchable(bool touchable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
-    }
-
-    void setFocusable(bool focusable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
-    }
-
-    void setVisible(bool visible) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
-    }
-
-    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
-        mInfo.dispatchingTimeout = timeout;
-    }
-
-    void setPaused(bool paused) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
-    }
-
-    void setPreventSplitting(bool preventSplitting) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
-    }
-
-    void setSlippery(bool slippery) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
-    }
-
-    void setWatchOutsideTouch(bool watchOutside) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
-    }
-
-    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
-
-    void setInterceptsStylus(bool interceptsStylus) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
-    }
-
-    void setDropInput(bool dropInput) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
-    }
-
-    void setDropInputIfObscured(bool dropInputIfObscured) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
-    }
-
-    void setNoInputChannel(bool noInputChannel) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
-    }
-
-    void setDisableUserActivity(bool disableUserActivity) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
-    }
-
-    void setAlpha(float alpha) { mInfo.alpha = alpha; }
-
-    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
-
-    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
-
-    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
-        mInfo.frame.left = frame.left;
-        mInfo.frame.top = frame.top;
-        mInfo.frame.right = frame.right;
-        mInfo.frame.bottom = frame.bottom;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(frame);
-
-        const Rect logicalDisplayFrame = displayTransform.transform(frame);
-        ui::Transform translate;
-        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
-        mInfo.transform = translate * displayTransform;
-    }
-
-    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
-
-    void setIsWallpaper(bool isWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
-    }
-
-    void setDupTouchToWallpaper(bool hasWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
-    }
-
-    void setTrustedOverlay(bool trustedOverlay) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
-    }
-
-    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
-        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
-    }
-
-    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
-
-    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
-
-    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) {
-        if (mInputReceiver == nullptr) {
-            return nullptr;
-        }
-        return mInputReceiver->consumeEvent(timeout);
-    }
-
-    void consumeMotion() {
-        std::unique_ptr<InputEvent> event = consume(100ms);
-
-        if (event == nullptr) {
-            LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one.";
-            return;
-        }
-
-        if (event->getType() != InputEventType::MOTION) {
-            LOG(FATAL) << mName << " expected a MotionEvent, got " << *event;
-            return;
-        }
-    }
-
-    sp<IBinder> getToken() { return mInfo.token; }
-
-    const std::string& getName() { return mName; }
-
-    void setOwnerInfo(Pid ownerPid, Uid ownerUid) {
-        mInfo.ownerPid = ownerPid;
-        mInfo.ownerUid = ownerUid;
-    }
-
-    Pid getPid() const { return mInfo.ownerPid; }
-
-    void destroyReceiver() { mInputReceiver = nullptr; }
-
-private:
-    FakeWindowHandle(std::string name) : mName(name){};
-    const std::string mName;
-    std::shared_ptr<FakeInputReceiver> mInputReceiver;
-    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
-    friend class sp<FakeWindowHandle>;
-};
-
-std::atomic<int32_t> FakeWindowHandle::sId{1};
-
-} // namespace inputdispatcher
-
-} // namespace android
diff --git a/services/inputflinger/tests/FakeWindows.cpp b/services/inputflinger/tests/FakeWindows.cpp
new file mode 100644
index 0000000..b116521
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindows.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright 2024 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 "FakeWindows.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- FakeInputReceiver ---
+
+FakeInputReceiver::FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel,
+                                     const std::string name)
+      : mConsumer(std::move(clientChannel)), mName(name) {}
+
+std::unique_ptr<InputEvent> FakeInputReceiver::consume(std::chrono::milliseconds timeout,
+                                                       bool handled) {
+    auto [consumeSeq, event] = receiveEvent(timeout);
+    if (!consumeSeq) {
+        return nullptr;
+    }
+    finishEvent(*consumeSeq, handled);
+    return std::move(event);
+}
+
+std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> FakeInputReceiver::receiveEvent(
+        std::chrono::milliseconds timeout) {
+    uint32_t consumeSeq;
+    std::unique_ptr<InputEvent> event;
+
+    std::chrono::time_point start = std::chrono::steady_clock::now();
+    status_t status = WOULD_BLOCK;
+    while (status == WOULD_BLOCK) {
+        InputEvent* rawEventPtr = nullptr;
+        status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+                                   &rawEventPtr);
+        event = std::unique_ptr<InputEvent>(rawEventPtr);
+        std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+        if (elapsed > timeout) {
+            break;
+        }
+    }
+
+    if (status == WOULD_BLOCK) {
+        // Just means there's no event available.
+        return std::make_pair(std::nullopt, nullptr);
+    }
+
+    if (status != OK) {
+        ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
+        return std::make_pair(std::nullopt, nullptr);
+    }
+    if (event == nullptr) {
+        ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
+    }
+    return std::make_pair(consumeSeq, std::move(event));
+}
+
+void FakeInputReceiver::finishEvent(uint32_t consumeSeq, bool handled) {
+    const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
+    ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
+}
+
+void FakeInputReceiver::sendTimeline(int32_t inputEventId,
+                                     std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+    const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
+    ASSERT_EQ(OK, status);
+}
+
+void FakeInputReceiver::consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
+                                     std::optional<ui::LogicalDisplayId> expectedDisplayId,
+                                     std::optional<int32_t> expectedFlags) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(expectedEventType, event->getType())
+            << mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
+            << " event, got " << *event;
+
+    if (expectedDisplayId.has_value()) {
+        EXPECT_EQ(expectedDisplayId, event->getDisplayId());
+    }
+
+    switch (expectedEventType) {
+        case InputEventType::KEY: {
+            const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
+            ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
+            if (expectedFlags.has_value()) {
+                EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
+            }
+            break;
+        }
+        case InputEventType::MOTION: {
+            const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
+            ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
+            if (expectedFlags.has_value()) {
+                EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
+            }
+            break;
+        }
+        case InputEventType::FOCUS: {
+            FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
+        }
+        case InputEventType::CAPTURE: {
+            FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
+        }
+        case InputEventType::TOUCH_MODE: {
+            FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
+        }
+        case InputEventType::DRAG: {
+            FAIL() << "Use 'consumeDragEvent' for DRAG events";
+        }
+    }
+}
+
+std::unique_ptr<MotionEvent> FakeInputReceiver::consumeMotion() {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+
+    if (event == nullptr) {
+        ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
+        return nullptr;
+    }
+
+    if (event->getType() != InputEventType::MOTION) {
+        ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
+        return nullptr;
+    }
+    return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
+}
+
+void FakeInputReceiver::consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+    std::unique_ptr<MotionEvent> motionEvent = consumeMotion();
+    ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
+    ASSERT_THAT(*motionEvent, matcher);
+}
+
+void FakeInputReceiver::consumeFocusEvent(bool hasFocus, bool inTouchMode) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::FOCUS, event->getType()) << "Instead of FocusEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
+    EXPECT_EQ(hasFocus, focusEvent.getHasFocus());
+}
+
+void FakeInputReceiver::consumeCaptureEvent(bool hasCapture) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::CAPTURE, event->getType())
+            << "Instead of CaptureEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
+    EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
+}
+
+void FakeInputReceiver::consumeDragEvent(bool isExiting, float x, float y) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::DRAG, event->getType()) << "Instead of DragEvent, got " << *event;
+
+    EXPECT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+
+    const auto& dragEvent = static_cast<const DragEvent&>(*event);
+    EXPECT_EQ(isExiting, dragEvent.isExiting());
+    EXPECT_EQ(x, dragEvent.getX());
+    EXPECT_EQ(y, dragEvent.getY());
+}
+
+void FakeInputReceiver::consumeTouchModeEvent(bool inTouchMode) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    ASSERT_NE(nullptr, event) << mName.c_str() << ": consumer should have returned non-NULL event.";
+    ASSERT_EQ(InputEventType::TOUCH_MODE, event->getType())
+            << "Instead of TouchModeEvent, got " << *event;
+
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, event->getDisplayId())
+            << mName.c_str() << ": event displayId should always be NONE.";
+    const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
+    EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
+}
+
+void FakeInputReceiver::assertNoEvents(std::chrono::milliseconds timeout) {
+    std::unique_ptr<InputEvent> event = consume(timeout);
+    if (event == nullptr) {
+        return;
+    }
+    if (event->getType() == InputEventType::KEY) {
+        KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
+        ADD_FAILURE() << "Received key event " << keyEvent;
+    } else if (event->getType() == InputEventType::MOTION) {
+        MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
+        ADD_FAILURE() << "Received motion event " << motionEvent;
+    } 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() == InputEventType::CAPTURE) {
+        const auto& captureEvent = static_cast<CaptureEvent&>(*event);
+        ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
+                      << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
+    } 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");
+    }
+    FAIL() << mName.c_str()
+           << ": should not have received any events, so consume() should return NULL";
+}
+
+sp<IBinder> FakeInputReceiver::getToken() {
+    return mConsumer.getChannel()->getConnectionToken();
+}
+
+int FakeInputReceiver::getChannelFd() {
+    return mConsumer.getChannel()->getFd();
+}
+
+// --- FakeWindowHandle ---
+
+std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)>
+        FakeWindowHandle::sOnEventReceivedCallback{};
+
+std::atomic<int32_t> FakeWindowHandle::sId{1};
+
+FakeWindowHandle::FakeWindowHandle(
+        const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+        const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher, const std::string name,
+        ui::LogicalDisplayId displayId, bool createInputChannel)
+      : mName(name) {
+    sp<IBinder> token;
+    if (createInputChannel) {
+        base::Result<std::unique_ptr<InputChannel>> channel = dispatcher->createInputChannel(name);
+        token = (*channel)->getConnectionToken();
+        mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+    }
+
+    inputApplicationHandle->updateInfo();
+    mInfo.applicationInfo = *inputApplicationHandle->getInfo();
+
+    mInfo.token = token;
+    mInfo.id = sId++;
+    mInfo.name = name;
+    mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
+    mInfo.alpha = 1.0;
+    mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
+    mInfo.transform.set(0, 0);
+    mInfo.globalScaleFactor = 1.0;
+    mInfo.touchableRegion.clear();
+    mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
+    mInfo.ownerPid = WINDOW_PID;
+    mInfo.ownerUid = WINDOW_UID;
+    mInfo.displayId = displayId;
+    mInfo.inputConfig = InputConfig::DEFAULT;
+}
+
+sp<FakeWindowHandle> FakeWindowHandle::clone(ui::LogicalDisplayId displayId) {
+    sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+    handle->mInfo = mInfo;
+    handle->mInfo.displayId = displayId;
+    handle->mInfo.id = sId++;
+    handle->mInputReceiver = mInputReceiver;
+    return handle;
+}
+
+std::unique_ptr<KeyEvent> FakeWindowHandle::consumeKey(bool handled) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
+    if (event == nullptr) {
+        ADD_FAILURE() << "No event";
+        return nullptr;
+    }
+    if (event->getType() != InputEventType::KEY) {
+        ADD_FAILURE() << "Instead of key event, got " << event;
+        return nullptr;
+    }
+    return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release()));
+}
+
+std::unique_ptr<MotionEvent> FakeWindowHandle::consumeMotionEvent(
+        const ::testing::Matcher<MotionEvent>& matcher) {
+    std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    if (event == nullptr) {
+        std::ostringstream matcherDescription;
+        matcher.DescribeTo(&matcherDescription);
+        ADD_FAILURE() << "No event (expected " << matcherDescription.str() << ") on " << mName;
+        return nullptr;
+    }
+    if (event->getType() != InputEventType::MOTION) {
+        ADD_FAILURE() << "Instead of motion event, got " << *event << " on " << mName;
+        return nullptr;
+    }
+    std::unique_ptr<MotionEvent> motionEvent =
+            std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
+    if (motionEvent == nullptr) {
+        return nullptr;
+    }
+    EXPECT_THAT(*motionEvent, matcher) << " on " << mName;
+    return motionEvent;
+}
+
+void FakeWindowHandle::assertNoEvents(std::optional<std::chrono::milliseconds> timeout) {
+    if (mInputReceiver == nullptr && mInfo.inputConfig.test(InputConfig::NO_INPUT_CHANNEL)) {
+        return; // Can't receive events if the window does not have input channel
+    }
+    ASSERT_NE(nullptr, mInputReceiver)
+            << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL";
+    mInputReceiver->assertNoEvents(timeout.value_or(CONSUME_TIMEOUT_NO_EVENT_EXPECTED));
+}
+
+std::unique_ptr<InputEvent> FakeWindowHandle::consume(std::chrono::milliseconds timeout,
+                                                      bool handled) {
+    if (mInputReceiver == nullptr) {
+        LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
+    }
+    std::unique_ptr<InputEvent> event = mInputReceiver->consume(timeout, handled);
+    if (event == nullptr) {
+        ADD_FAILURE() << "Consume failed: no event";
+    }
+
+    if (sOnEventReceivedCallback != nullptr) {
+        sOnEventReceivedCallback(event, mInfo);
+    }
+    return event;
+}
+
+std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>>
+FakeWindowHandle::receive() {
+    if (mInputReceiver == nullptr) {
+        ADD_FAILURE() << "Invalid receive event on window with no receiver";
+        return std::make_pair(std::nullopt, nullptr);
+    }
+    auto out = mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+    const auto& [_, event] = out;
+
+    if (sOnEventReceivedCallback != nullptr) {
+        sOnEventReceivedCallback(event, mInfo);
+    }
+    return out;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h
new file mode 100644
index 0000000..3a3238a
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2024 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 "../dispatcher/InputDispatcher.h"
+#include "TestEventMatchers.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputConsumer.h>
+
+namespace android {
+
+/**
+ * If we expect to receive the event, the timeout can be made very long. When the test are running
+ * correctly, we will actually never wait until the end of the timeout because the wait will end
+ * when the event comes in. Still, this value shouldn't be infinite. During development, a local
+ * change may cause the test to fail. This timeout should be short enough to not annoy so that the
+ * developer can see the failure quickly (on human scale).
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
+
+/**
+ * When no event is expected, we can have a very short timeout. A large value here would slow down
+ * the tests. In the unlikely event of system being too slow, the event may still be present but the
+ * timeout would complete before it is consumed. This would result in test flakiness. If this
+ * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
+ * would get noticed and addressed quickly.
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
+
+/**
+ * The default pid and uid for windows created on the primary display by the test.
+ */
+static constexpr gui::Pid WINDOW_PID{999};
+static constexpr gui::Uid WINDOW_UID{1001};
+
+/**
+ * Default input dispatching timeout if there is no focused application or paused window
+ * from which to determine an appropriate dispatching timeout.
+ */
+static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+        android::base::HwTimeoutMultiplier());
+
+// --- FakeInputReceiver ---
+
+class FakeInputReceiver {
+public:
+    explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name);
+
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = false);
+    /**
+     * Receive an event without acknowledging it.
+     * Return the sequence number that could later be used to send finished signal.
+     */
+    std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent(
+            std::chrono::milliseconds timeout);
+    /**
+     * To be used together with "receiveEvent" to complete the consumption of an event.
+     */
+    void finishEvent(uint32_t consumeSeq, bool handled = true);
+
+    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+    void consumeEvent(android::InputEventType expectedEventType, int32_t expectedAction,
+                      std::optional<ui::LogicalDisplayId> expectedDisplayId,
+                      std::optional<int32_t> expectedFlags);
+
+    std::unique_ptr<MotionEvent> consumeMotion();
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher);
+
+    void consumeFocusEvent(bool hasFocus, bool inTouchMode);
+    void consumeCaptureEvent(bool hasCapture);
+    void consumeDragEvent(bool isExiting, float x, float y);
+    void consumeTouchModeEvent(bool inTouchMode);
+
+    void assertNoEvents(std::chrono::milliseconds timeout);
+
+    sp<IBinder> getToken();
+    int getChannelFd();
+
+private:
+    InputConsumer mConsumer;
+    DynamicInputEventFactory mEventFactory;
+    std::string mName;
+};
+
+// --- FakeWindowHandle ---
+
+class FakeWindowHandle : public gui::WindowInfoHandle {
+public:
+    static const int32_t WIDTH = 600;
+    static const int32_t HEIGHT = 800;
+    using InputConfig = gui::WindowInfo::InputConfig;
+
+    // This is a callback that is fired when an event is received by the window.
+    // It is static to avoid having to pass it individually into all of the FakeWindowHandles
+    // created by tests.
+    // TODO(b/210460522): Update the tests to use a factory pattern so that we can avoid
+    //   the need to make this static.
+    static std::function<void(const std::unique_ptr<InputEvent>&, const gui::WindowInfo&)>
+            sOnEventReceivedCallback;
+
+    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+                     const std::unique_ptr<inputdispatcher::InputDispatcher>& dispatcher,
+                     const std::string name, ui::LogicalDisplayId displayId,
+                     bool createInputChannel = true);
+
+    sp<FakeWindowHandle> clone(ui::LogicalDisplayId displayId);
+
+    inline void setTouchable(bool touchable) {
+        mInfo.setInputConfig(InputConfig::NOT_TOUCHABLE, !touchable);
+    }
+
+    inline void setFocusable(bool focusable) {
+        mInfo.setInputConfig(InputConfig::NOT_FOCUSABLE, !focusable);
+    }
+
+    inline void setVisible(bool visible) {
+        mInfo.setInputConfig(InputConfig::NOT_VISIBLE, !visible);
+    }
+
+    inline void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+        mInfo.dispatchingTimeout = timeout;
+    }
+
+    inline void setPaused(bool paused) {
+        mInfo.setInputConfig(InputConfig::PAUSE_DISPATCHING, paused);
+    }
+
+    inline void setPreventSplitting(bool preventSplitting) {
+        mInfo.setInputConfig(InputConfig::PREVENT_SPLITTING, preventSplitting);
+    }
+
+    inline void setSlippery(bool slippery) {
+        mInfo.setInputConfig(InputConfig::SLIPPERY, slippery);
+    }
+
+    inline void setWatchOutsideTouch(bool watchOutside) {
+        mInfo.setInputConfig(InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+    }
+
+    inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); }
+
+    inline void setSecure(bool secure) {
+        if (secure) {
+            mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE;
+        } else {
+            using namespace ftl::flag_operators;
+            mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE;
+        }
+        mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_PRIVACY, secure);
+    }
+
+    inline void setInterceptsStylus(bool interceptsStylus) {
+        mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+    }
+
+    inline void setDropInput(bool dropInput) {
+        mInfo.setInputConfig(InputConfig::DROP_INPUT, dropInput);
+    }
+
+    inline void setDropInputIfObscured(bool dropInputIfObscured) {
+        mInfo.setInputConfig(InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+    }
+
+    inline void setNoInputChannel(bool noInputChannel) {
+        mInfo.setInputConfig(InputConfig::NO_INPUT_CHANNEL, noInputChannel);
+    }
+
+    inline void setDisableUserActivity(bool disableUserActivity) {
+        mInfo.setInputConfig(InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
+    }
+
+    inline void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) {
+        mInfo.setInputConfig(InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH, shouldGlobalStylusBlockTouch);
+    }
+
+    inline void setAlpha(float alpha) { mInfo.alpha = alpha; }
+
+    inline void setTouchOcclusionMode(gui::TouchOcclusionMode mode) {
+        mInfo.touchOcclusionMode = mode;
+    }
+
+    inline void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
+    inline void setFrame(const Rect& frame,
+                         const ui::Transform& displayTransform = ui::Transform()) {
+        mInfo.frame = frame;
+        mInfo.touchableRegion.clear();
+        mInfo.addTouchableRegion(frame);
+
+        const Rect logicalDisplayFrame = displayTransform.transform(frame);
+        ui::Transform translate;
+        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+        mInfo.transform = translate * displayTransform;
+    }
+
+    inline void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
+    inline void setIsWallpaper(bool isWallpaper) {
+        mInfo.setInputConfig(InputConfig::IS_WALLPAPER, isWallpaper);
+    }
+
+    inline void setDupTouchToWallpaper(bool hasWallpaper) {
+        mInfo.setInputConfig(InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+    }
+
+    inline void setTrustedOverlay(bool trustedOverlay) {
+        mInfo.setInputConfig(InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+    }
+
+    inline void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
+        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
+    }
+
+    inline void setWindowScale(float xScale, float yScale) {
+        setWindowTransform(xScale, 0, 0, yScale);
+    }
+
+    inline void setWindowOffset(float offsetX, float offsetY) {
+        mInfo.transform.set(offsetX, offsetY);
+    }
+
+    std::unique_ptr<KeyEvent> consumeKey(bool handled = true);
+
+    inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+        std::unique_ptr<KeyEvent> keyEvent = consumeKey();
+        EXPECT_NE(nullptr, keyEvent);
+        if (!keyEvent) {
+            return nullptr;
+        }
+        EXPECT_THAT(*keyEvent, matcher);
+        return keyEvent;
+    }
+
+    inline void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
+        consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                       WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
+    }
+
+    inline void consumeKeyUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
+        consumeKeyEvent(testing::AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
+                                       WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionCancel(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
+    }
+
+    inline void consumeMotionMove(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionDown(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeAnyMotionDown(expectedDisplayId, expectedFlags);
+    }
+
+    inline void consumeAnyMotionDown(
+            std::optional<ui::LogicalDisplayId> expectedDisplayId = std::nullopt,
+            std::optional<int32_t> expectedFlags = std::nullopt) {
+        consumeMotionEvent(
+                testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                               testing::Conditional(expectedDisplayId.has_value(),
+                                                    WithDisplayId(*expectedDisplayId), testing::_),
+                               testing::Conditional(expectedFlags.has_value(),
+                                                    WithFlags(*expectedFlags), testing::_)));
+    }
+
+    inline void consumeMotionPointerDown(
+            int32_t pointerIdx,
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionPointerDown(int32_t pointerIdx,
+                                         const ::testing::Matcher<MotionEvent>& matcher) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
+    }
+
+    inline void consumeMotionPointerUp(int32_t pointerIdx,
+                                       const ::testing::Matcher<MotionEvent>& matcher) {
+        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
+                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
+    }
+
+    inline void consumeMotionUp(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionOutside(
+            ui::LogicalDisplayId expectedDisplayId = ui::LogicalDisplayId::DEFAULT,
+            int32_t expectedFlags = 0) {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                          WithDisplayId(expectedDisplayId),
+                                          WithFlags(expectedFlags)));
+    }
+
+    inline void consumeMotionOutsideWithZeroedCoords() {
+        consumeMotionEvent(testing::AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
+                                          WithRawCoords(0, 0)));
+    }
+
+    inline void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
+    }
+
+    inline void consumeCaptureEvent(bool hasCapture) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeCaptureEvent(hasCapture);
+    }
+
+    std::unique_ptr<MotionEvent> consumeMotionEvent(
+            const ::testing::Matcher<MotionEvent>& matcher = testing::_);
+
+    inline void consumeDragEvent(bool isExiting, float x, float y) {
+        mInputReceiver->consumeDragEvent(isExiting, x, y);
+    }
+
+    inline void consumeTouchModeEvent(bool inTouchMode) {
+        ASSERT_NE(mInputReceiver, nullptr)
+                << "Cannot consume events from a window with no receiver";
+        mInputReceiver->consumeTouchModeEvent(inTouchMode);
+    }
+
+    inline std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() {
+        return receive();
+    }
+
+    inline void finishEvent(uint32_t sequenceNum) {
+        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+        mInputReceiver->finishEvent(sequenceNum);
+    }
+
+    inline void sendTimeline(int32_t inputEventId,
+                             std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
+        mInputReceiver->sendTimeline(inputEventId, timeline);
+    }
+
+    void assertNoEvents(std::optional<std::chrono::milliseconds> timeout = {});
+
+    inline sp<IBinder> getToken() { return mInfo.token; }
+
+    inline const std::string& getName() { return mName; }
+
+    inline void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) {
+        mInfo.ownerPid = ownerPid;
+        mInfo.ownerUid = ownerUid;
+    }
+
+    inline gui::Pid getPid() const { return mInfo.ownerPid; }
+
+    inline void destroyReceiver() { mInputReceiver = nullptr; }
+
+    inline int getChannelFd() { return mInputReceiver->getChannelFd(); }
+
+    // FakeWindowHandle uses this consume method to ensure received events are added to the trace.
+    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = true);
+
+private:
+    FakeWindowHandle(std::string name) : mName(name){};
+    const std::string mName;
+    std::shared_ptr<FakeInputReceiver> mInputReceiver;
+    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+    friend class sp<FakeWindowHandle>;
+
+    // FakeWindowHandle uses this receive method to ensure received events are added to the trace.
+    std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>> receive();
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 2ff9c3c..f794da5 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -20,6 +20,7 @@
 
 #define ASSERT_FOCUS_CHANGE(_changes, _oldFocus, _newFocus) \
     {                                                       \
+        ASSERT_TRUE(_changes.has_value());                  \
         ASSERT_EQ(_oldFocus, _changes->oldFocus);           \
         ASSERT_EQ(_newFocus, _changes->newFocus);           \
     }
@@ -73,7 +74,7 @@
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
-    ASSERT_EQ(request.displayId, changes->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId);
 
     // invisible window cannot get focused
     request.token = invisibleWindowToken;
@@ -152,6 +153,39 @@
     ASSERT_FOCUS_CHANGE(changes, /*from*/ invisibleWindowToken, /*to*/ nullptr);
 }
 
+TEST(FocusResolverTest, FocusTransferToMirror) {
+    sp<IBinder> focusableWindowToken = sp<BBinder>::make();
+    auto window = sp<FakeWindowHandle>::make("Window", focusableWindowToken,
+                                             /*focusable=*/true, /*visible=*/true);
+    auto mirror = sp<FakeWindowHandle>::make("Mirror", focusableWindowToken,
+                                             /*focusable=*/true, /*visible=*/true);
+
+    FocusRequest request;
+    request.displayId = 42;
+    request.token = focusableWindowToken;
+    FocusResolver focusResolver;
+    std::optional<FocusResolver::FocusChanges> changes =
+            focusResolver.setFocusedWindow(request, {window, mirror});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ focusableWindowToken);
+
+    // The mirror window now comes on top, and the focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId},
+                                            {mirror, window});
+    ASSERT_FALSE(changes.has_value());
+
+    // The window now comes on top while the mirror is removed, and the focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {window});
+    ASSERT_FALSE(changes.has_value());
+
+    // The window is removed but the mirror is on top, and focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {mirror});
+    ASSERT_FALSE(changes.has_value());
+
+    // All windows removed
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
+    ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
+}
+
 TEST(FocusResolverTest, SetInputWindows) {
     sp<IBinder> focusableWindowToken = sp<BBinder>::make();
     std::vector<sp<WindowInfoHandle>> windows;
@@ -169,9 +203,13 @@
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_EQ(focusableWindowToken, changes->newFocus);
 
+    // When there are no changes to the window, focus does not change
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
+    ASSERT_FALSE(changes.has_value());
+
     // Window visibility changes and the window loses focus
     window->setVisible(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ focusableWindowToken, /*to*/ nullptr);
 }
 
@@ -195,7 +233,7 @@
 
     // Window visibility changes and the window gets focused
     invisibleWindow->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ invisibleWindowToken);
 }
 
@@ -219,25 +257,25 @@
 
     // Focusability changes and the window gets focused
     window->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 
     // Visibility changes and the window loses focus
     window->setVisible(false);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
 
     // Visibility changes and the window gets focused
     window->setVisible(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 
     // Window is gone and the window loses focus
-    changes = focusResolver.setInputWindows(request.displayId, {});
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
 
     // Window returns and the window gains focus
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
 }
 
@@ -270,27 +308,27 @@
 
     // Embedded is now focusable so will gain focus
     embeddedWindow->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{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);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{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);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{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);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{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);
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FALSE(changes);
 }
 
@@ -378,21 +416,16 @@
     std::optional<FocusResolver::FocusChanges> changes =
             focusResolver.setFocusedWindow(request, windows);
     ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
-    ASSERT_EQ(request.displayId, changes->displayId);
-
-    // Start with a focused window
-    window->setFocusable(true);
-    changes = focusResolver.setInputWindows(request.displayId, windows);
-    ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
+    ASSERT_EQ(ui::LogicalDisplayId{request.displayId}, changes->displayId);
 
     // When a display is removed, all windows are removed from the display
     // and our focused window loses focus
-    changes = focusResolver.setInputWindows(request.displayId, {});
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, {});
     ASSERT_FOCUS_CHANGE(changes, /*from*/ windowToken, /*to*/ nullptr);
-    focusResolver.displayRemoved(request.displayId);
+    focusResolver.displayRemoved(ui::LogicalDisplayId{request.displayId});
 
-    // When a display is readded, the window does not get focus since the request was cleared.
-    changes = focusResolver.setInputWindows(request.displayId, windows);
+    // When a display is re-added, the window does not get focus since the request was cleared.
+    changes = focusResolver.setInputWindows(ui::LogicalDisplayId{request.displayId}, windows);
     ASSERT_FALSE(changes);
 }
 
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 337b52b..225ae0f 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -20,7 +20,6 @@
 #include <flag_macros.h>
 #include <gestures/GestureConverter.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
@@ -51,13 +50,11 @@
 using testing::ElementsAre;
 using testing::VariantWith;
 
-class GestureConverterTestBase : public testing::Test {
+class GestureConverterTest : public testing::Test {
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
-    static constexpr float POINTER_X = 500;
-    static constexpr float POINTER_Y = 200;
 
     void SetUp() {
         mFakeEventHub = std::make_unique<FakeEventHub>();
@@ -68,12 +65,6 @@
         mDevice = newDevice();
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20);
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20);
-
-        mFakePointerController = std::make_shared<FakePointerController>(
-                /*enabled=*/!input_flags::enable_pointer_choreographer());
-        mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-        mFakePointerController->setPosition(POINTER_X, POINTER_Y);
-        mFakePolicy->setPointerController(mFakePointerController);
     }
 
     std::shared_ptr<InputDevice> newDevice() {
@@ -96,21 +87,12 @@
     std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
     std::shared_ptr<InputDevice> mDevice;
-    std::shared_ptr<FakePointerController> mFakePointerController;
-};
-
-class GestureConverterTest : public GestureConverterTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        GestureConverterTestBase::SetUp();
-    }
 };
 
 TEST_F(GestureConverterTest, Move) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args =
@@ -118,38 +100,31 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0, 0))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithCoords(POINTER_X - 5, POINTER_Y + 10),
                                           WithRelativeMotion(-5, 10), WithButtonState(0),
                                           WithPressure(0.0f)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
-
-    // The same gesture again should only repeat the HOVER_MOVE and cursor position change, not the
-    // HOVER_ENTER.
+    // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER.
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                              WithCoords(POINTER_X - 10, POINTER_Y + 20),
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
                               WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
                               WithButtonState(0), WithPressure(0.0f),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 10, POINTER_Y + 20));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Move_Rotated) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args =
@@ -157,24 +132,21 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
                                           WithRelativeMotion(0, 0))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithCoords(POINTER_X + 10, POINTER_Y + 5),
                                           WithRelativeMotion(10, 5), WithButtonState(0),
                                           WithPressure(0.0f)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ButtonsChange) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     // Press left and right buttons at once
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -197,9 +169,9 @@
                                           WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
                                                           AMOTION_EVENT_BUTTON_SECONDARY)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Then release the left button
     Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -210,9 +182,9 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         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),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Finally release the right button
     Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -228,16 +200,15 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0),
-                                                         WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ButtonDownAfterMoveExitsHover) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args =
@@ -250,14 +221,14 @@
     ASSERT_THAT(args.front(),
                 VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
-                              WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                              WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT))));
+                              WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     // Press the button
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -274,22 +245,19 @@
                                           WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
                                           WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Move
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(POINTER_X - 5, POINTER_Y + 10), WithRelativeMotion(-5, 10),
-                              WithToolType(ToolType::FINGER),
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
                               WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Release the button
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -305,17 +273,18 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0),
-                                                         WithCoords(POINTER_X - 5, POINTER_Y + 10),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     const nsecs_t downTime = 12345;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args =
@@ -323,31 +292,32 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithDownTime(downTime))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithCoords(POINTER_X, POINTER_Y - 10),
+                                          WithCoords(0, -10),
                                           WithGestureScrollDistance(0, 10, EPSILON)))));
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                        AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(POINTER_X, POINTER_Y - 15),
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
                               WithGestureScrollDistance(0, 5, EPSILON),
                               WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                               WithToolType(ToolType::FINGER),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                        AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
@@ -355,18 +325,20 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(POINTER_X, POINTER_Y - 15),
+                                          WithCoords(0, -15),
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithMotionClassification(
                                                   MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_Rotated) {
@@ -374,7 +346,7 @@
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args =
@@ -382,52 +354,53 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithDownTime(downTime))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithCoords(POINTER_X - 10, POINTER_Y),
+                                          WithCoords(-10, 0),
                                           WithGestureScrollDistance(0, 10, EPSILON)))));
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithCoords(POINTER_X - 15, POINTER_Y),
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
                               WithGestureScrollDistance(0, 5, EPSILON),
                               WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
                          GESTURES_FLING_START);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(POINTER_X - 15, POINTER_Y),
+                                          WithCoords(-15, 0),
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithMotionClassification(
                                                   MotionClassification::TWO_FINGER_SWIPE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args =
@@ -445,13 +418,13 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::NONE),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args =
@@ -473,10 +446,47 @@
     EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
 }
 
+TEST_F(GestureConverterTest, Scroll_ClearsFakeFingerPositionOnSubsequentScrollGestures) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 15, -10);
+    std::list<NotifyArgs> args =
+            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -2, -5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
+    Gesture flingGestureEnd(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 0,
+                            GESTURES_FLING_TAP_DOWN);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGestureEnd);
+
+    // Start a second scoll gesture, and ensure the fake finger is reset to (0, 0), instead of
+    // continuing from the position where the last scroll gesture's fake finger ended.
+    Gesture secondScrollStart(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 2,
+                              14);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, secondScrollStart);
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                          WithCoords(0, 0),
+                                          WithGestureScrollDistance(0, 0, EPSILON))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithCoords(2, 14),
+                                          WithGestureScrollDistance(-2, -14, EPSILON)))));
+}
+
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/0);
@@ -497,7 +507,7 @@
 TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
                          /*dy=*/5);
@@ -524,7 +534,7 @@
     // only checks movement in one dimension.
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
@@ -535,7 +545,7 @@
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                               WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
@@ -581,7 +591,7 @@
                       WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                       WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
     EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
     EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
@@ -619,25 +629,27 @@
                                           WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
                          /* dy= */ 10);
     std::list<NotifyArgs> args =
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
     ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 
     // Three fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
@@ -681,7 +693,7 @@
     ASSERT_THAT(arg,
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                       WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     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);
@@ -706,14 +718,15 @@
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
-    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))));
+    ASSERT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ui::LogicalDisplayId::DEFAULT))));
 }
 
 TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                          /* dx= */ 10, /* dy= */ 0);
@@ -724,7 +737,7 @@
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                               WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Four fake fingers should be created. We don't actually care where they are, so long as they
     // move appropriately.
@@ -779,7 +792,7 @@
                       WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
                       WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
                       WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     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);
@@ -828,17 +841,20 @@
                                           WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
@@ -847,20 +863,19 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(POINTER_X - 100, POINTER_Y),
-                                          WithPointerCount(1u))),
+                                          WithCoords(-100, 0), WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(
                                                   AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
-                                          WithPointerCount(2u)))));
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
@@ -870,10 +885,10 @@
                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                               WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(0.8f, EPSILON),
-                              WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
-                              WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
+                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -885,25 +900,30 @@
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
+                                          WithPointerCount(2u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
+                                          WithPointerCount(1u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                          GESTURES_ZOOM_START);
@@ -912,33 +932,32 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(POINTER_X - 100, POINTER_Y),
-                                          WithPointerCount(1u))),
+                                          WithCoords(-100, 0), WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(
                                                   AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithPointerCoords(1, POINTER_X + 100, POINTER_Y),
-                                          WithPointerCount(2u)))));
+                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                               WithMotionClassification(MotionClassification::PINCH),
-                              WithGesturePinchScaleFactor(1.2f, EPSILON),
-                              WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
-                              WithPointerCoords(1, POINTER_X + 120, POINTER_Y),
+                              WithGesturePinchScaleFactor(1.1f, EPSILON),
+                              WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
                               WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -950,25 +969,28 @@
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
+                                          WithPointerCount(2u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
+                                          WithPointerCount(1u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -993,7 +1015,7 @@
 TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -1020,7 +1042,7 @@
 TEST_F(GestureConverterTest, ResetWithButtonPressed) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                         /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
@@ -1044,15 +1066,17 @@
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
@@ -1061,24 +1085,26 @@
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(POINTER_X, POINTER_Y - 10),
+                                          WithCoords(0, -10),
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithMotionClassification(
                                                   MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
                          /*dy=*/10);
@@ -1112,14 +1138,15 @@
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, ResetDuringPinch) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
                          GESTURES_ZOOM_START);
@@ -1141,17 +1168,18 @@
                                           WithPointerCount(1u))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(POINTER_X, POINTER_Y),
+                                          WithCoords(0, 0),
                                           WithMotionClassification(MotionClassification::NONE)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, FlingTapDown) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                            /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
@@ -1159,20 +1187,17 @@
             converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
 
     ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                      WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
-                      WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
-    ASSERT_TRUE(mFakePointerController->isPointerShown());
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0),
+                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+                      WithButtonState(0), WithPressure(0.0f),
+                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 }
 
 TEST_F(GestureConverterTest, FlingTapDownAfterScrollStopsFling) {
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     input_flags::enable_touchpad_fling_stop(true);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
     std::list<NotifyArgs> args =
@@ -1192,19 +1217,19 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)),
                             VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithMotionClassification(MotionClassification::NONE)))));
+                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE)))));
 }
 
 TEST_F(GestureConverterTest, Tap) {
     // Tap should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1241,17 +1266,17 @@
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithButtonState(0), WithPressure(0.0f)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F(GestureConverterTest, Click) {
     // Click should produce button press/release events
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1278,15 +1303,16 @@
                                           WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                                           WithPressure(1.0f)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
                             /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
+
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
@@ -1300,9 +1326,9 @@
                                           WithPressure(0.0f)))));
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithButtonState(0), WithCoords(POINTER_X, POINTER_Y),
-                              WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                        AllOf(WithButtonState(0), WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 }
 
 TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled,
@@ -1315,7 +1341,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1344,7 +1370,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1426,7 +1452,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
                          /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
@@ -1438,6 +1464,7 @@
                               /* down= */ GESTURES_BUTTON_LEFT,
                               /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
     args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
+
     ASSERT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
@@ -1452,10 +1479,10 @@
                                           WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
                                           WithPressure(1.0f)))));
     ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                              WithToolType(ToolType::FINGER),
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                             /* down= */ GESTURES_BUTTON_NONE,
@@ -1466,18 +1493,23 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
                                           WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(0), WithPressure(1.0f))),
+                                          WithButtonState(0), WithCoords(0, 0),
+                                          WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(1.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0), WithPressure(0.0f))),
+                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithButtonState(0), WithPressure(0.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(POINTER_X, POINTER_Y),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
+                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
+                                          WithToolType(ToolType::FINGER), WithButtonState(0),
+                                          WithPressure(0.0f),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     // Future taps should be re-enabled
     ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
@@ -1490,7 +1522,7 @@
 
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args =
@@ -1506,1433 +1538,7 @@
     const nsecs_t gestureStartTime = 1000;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    // Start a move gesture at gestureStartTime
-    Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(gestureStartTime, READ_TIME, gestureStartTime, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
-
-    // Key presses with IME connection should cancel ongoing move gesture
-    nsecs_t currentTime = gestureStartTime + 100;
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    mReader->getContext()->setLastKeyDownTimestamp(currentTime);
-    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
-    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT))));
-
-    // any updates in existing move gesture should be ignored
-    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
-    args = converter.handleGesture(currentTime, READ_TIME, gestureStartTime, moveGesture);
-    ASSERT_EQ(0u, args.size());
-
-    // New gesture should not be affected
-    currentTime += 100;
-    moveGesture = Gesture(kGestureMove, currentTime, currentTime, -5, 10);
-    args = converter.handleGesture(currentTime, READ_TIME, currentTime, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE))));
-}
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(true);
-        GestureConverterTestBase::SetUp();
-    }
-};
-
-TEST_F(GestureConverterTestWithChoreographer, Move) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithRelativeMotion(0, 0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithRelativeMotion(-5, 10), WithButtonState(0),
-                                          WithPressure(0.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // The same gesture again should only repeat the HOVER_MOVE, not the HOVER_ENTER.
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
-                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                              WithButtonState(0), WithPressure(0.0f),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithRelativeMotion(0, 0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
-                                          WithRelativeMotion(10, 5), WithButtonState(0),
-                                          WithPressure(0.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    // Press left and right buttons at once
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
-                        /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                                          AMOTION_EVENT_BUTTON_SECONDARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
-                                                          AMOTION_EVENT_BUTTON_SECONDARY)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Then release the left button
-    Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
-                          /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, leftUpGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                              WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                              WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Finally release the right button
-    Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                           /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
-                           /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, rightUpGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ButtonDownAfterMoveExitsHover) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /*down=*/GESTURES_BUTTON_LEFT, /*up=*/GESTURES_BUTTON_NONE,
-                        /*is_tap=*/false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
-    ASSERT_THAT(args.front(),
-                VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT), WithButtonState(0),
-                              WithCoords(0, 0), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    // Press the button
-    Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                        /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
-                        /* is_tap= */ false);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, downGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Move
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
-                              WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
-                              WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Release the button
-    Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                      /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
-                      /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, upGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll) {
-    const nsecs_t downTime = 12345;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(0, 0),
-                                          WithGestureScrollDistance(0, 0, EPSILON),
-                                          WithDownTime(downTime))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithCoords(0, -10),
-                                          WithGestureScrollDistance(0, 10, EPSILON)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
-                              WithGestureScrollDistance(0, 5, EPSILON),
-                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithToolType(ToolType::FINGER),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0, -15),
-                                          WithGestureScrollDistance(0, 0, EPSILON),
-                                          WithMotionClassification(
-                                                  MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
-    const nsecs_t downTime = 12345;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(downTime, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(0, 0),
-                                          WithGestureScrollDistance(0, 0, EPSILON),
-                                          WithDownTime(downTime))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                          WithCoords(-10, 0),
-                                          WithGestureScrollDistance(0, 10, EPSILON)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
-                              WithGestureScrollDistance(0, 5, EPSILON),
-                              WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(-15, 0),
-                                          WithGestureScrollDistance(0, 0, EPSILON),
-                                          WithMotionClassification(
-                                                  MotionClassification::TWO_FINGER_SWIPE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::NONE),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like pinch.
-    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
-                         /*dy=*/0);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/-5,
-                        /*dy=*/10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionClassification(MotionClassification::NONE))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
-                         /*dy=*/5);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like pinch.
-    Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, pinchGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) {
-    // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
-    // start swiping up and then start moving left or right, it'll return gesture events with only Y
-    // deltas until you lift your fingers and start swiping again. That's why each of these tests
-    // only checks movement in one dimension.
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
-                         /* dy= */ 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                              WithGestureSwipeFingerCount(3), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Three fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
-
-    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(3u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
-    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
-    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(3),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(3u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(3),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(3),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setOrientation(ui::ROTATION_90);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
-                         /* dy= */ 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_EQ(4u, args.size());
-    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT))));
-
-    // Three fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
-    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);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-
-    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 0, /* dy= */ 5);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    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);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithGestureOffset(0, 0, EPSILON), WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER)))));
-    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithDisplayId(ADISPLAY_ID_DEFAULT))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                         /* dx= */ 10, /* dy= */ 0);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_EQ(5u, args.size());
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                              WithGestureSwipeFingerCount(4), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Four fake fingers should be created. We don't actually care where they are, so long as they
-    // move appropriately.
-    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
-                      WithPointerCount(1u)));
-    PointerCoords finger0Start = arg.pointerCoords[0];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
-    PointerCoords finger1Start = arg.pointerCoords[1];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
-    PointerCoords finger2Start = arg.pointerCoords[2];
-    args.pop_front();
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(4u)));
-    PointerCoords finger3Start = arg.pointerCoords[3];
-    args.pop_front();
-
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0.01, 0, EPSILON), WithPointerCount(4u)));
-    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);
-    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
-
-    Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* dx= */ 5, /* dy= */ 0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, continueGesture);
-    ASSERT_EQ(1u, args.size());
-    arg = std::get<NotifyMotionArgs>(args.front());
-    ASSERT_THAT(arg,
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                      WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
-                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
-                      WithPointerCount(4u), WithToolType(ToolType::FINGER),
-                      WithDisplayId(ADISPLAY_ID_DEFAULT)));
-    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);
-    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
-    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
-    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
-    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
-    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
-
-    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, liftGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(4),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(4u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(4),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(3u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(4),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithGestureSwipeFingerCount(4),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(-100, 0), WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::PINCH),
-                              WithGesturePinchScaleFactor(1.0f, EPSILON),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithMotionClassification(MotionClassification::PINCH),
-                              WithGesturePinchScaleFactor(0.8f, EPSILON),
-                              WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
-                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithCoords(-100, 0), WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_DOWN |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithPointerCoords(1, 100, 0), WithPointerCount(2u)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionClassification(MotionClassification::PINCH),
-                              WithGesturePinchScaleFactor(1.0f, EPSILON),
-                              WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                              WithMotionClassification(MotionClassification::PINCH),
-                              WithGesturePinchScaleFactor(1.1f, EPSILON),
-                              WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
-                              WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                        WithMotionClassification(MotionClassification::NONE))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                          /*dz=*/1.2, GESTURES_ZOOM_UPDATE);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, updateGesture);
-
-    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                       GESTURES_ZOOM_END);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, endGesture);
-
-    // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
-    // need to use another gesture type, like scroll.
-    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
-                          /*dy=*/0);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
-    ASSERT_FALSE(args.empty());
-    EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    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, ARBITRARY_TIME, downGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
-                                          WithButtonState(0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithButtonState(0)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0, -10),
-                                          WithGestureScrollDistance(0, 0, EPSILON),
-                                          WithMotionClassification(
-                                                  MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
-                         /*dy=*/10);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    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))),
-                            VariantWith<NotifyMotionArgs>(
-                                    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))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithGestureOffset(0, 0, EPSILON),
-                                          WithMotionClassification(
-                                                  MotionClassification::MULTI_FINGER_SWIPE),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
-                         GESTURES_ZOOM_START);
-    (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, startGesture);
-
-    std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(
-                                                  AMOTION_EVENT_ACTION_POINTER_UP |
-                                                  1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithMotionClassification(MotionClassification::PINCH),
-                                          WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
-
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER), WithCoords(0, 0),
-                      WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
-                      WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, FlingTapDownAfterScrollStopsFling) {
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    input_flags::enable_touchpad_fling_stop(true);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, scrollGesture);
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
-                         GESTURES_FLING_START);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-
-    Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                           /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapDownGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_DOWN)),
-                            VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithMotionClassification(MotionClassification::NONE)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Tap) {
-    // Tap should produce button press/release events
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-    // We don't need to check args here, since it's covered by the FlingTapDown test.
-
-    Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                       /* down= */ GESTURES_BUTTON_LEFT,
-                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, tapGesture);
-
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
-                                          WithButtonState(0), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(0), WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithButtonState(0), WithPressure(0.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F(GestureConverterTestWithChoreographer, Click) {
-    // Click should produce button press/release events
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-    // We don't need to check args here, since it's covered by the FlingTapDown test.
-
-    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                              /* down= */ GESTURES_BUTTON_LEFT,
-                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
-
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
-                                          WithButtonState(0), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* down= */ GESTURES_BUTTON_NONE,
-                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
-
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithPressure(0.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithButtonState(0), WithCoords(0, 0),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION),
-                  REQUIRES_FLAGS_DISABLED(TOUCHPAD_PALM_REJECTION_V2)) {
-    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
-
-    // Tap should be ignored when disabled
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
-    // We don't need to check args here, since it's covered by the FlingTapDown test.
-
-    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
-                       /* down= */ GESTURES_BUTTON_LEFT,
-                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
-
-    // no events should be generated
-    ASSERT_EQ(0u, args.size());
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabledWithDelay,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
-    nsecs_t currentTime = ARBITRARY_GESTURE_TIME;
-
-    // Tap should be ignored when disabled
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
-    // We don't need to check args here, since it's covered by the FlingTapDown test.
-
-    Gesture tapGesture(kGestureButtonsChange, currentTime, currentTime,
-                       /* down= */ GESTURES_BUTTON_LEFT,
-                       /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
-
-    // no events should be generated
-    ASSERT_EQ(0u, args.size());
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-
-    // taps before the threshold should still be ignored
-    currentTime += TAP_ENABLE_DELAY_NANOS.count();
-    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
-                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
-
-    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
-                         /* down= */ GESTURES_BUTTON_LEFT,
-                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
-
-    // no events should be generated
-    ASSERT_EQ(0u, args.size());
-
-    // taps after the threshold should be recognised
-    currentTime += 1;
-    flingGesture = Gesture(kGestureFling, currentTime, currentTime, /* vx= */ 0,
-                           /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, flingGesture);
-
-    ASSERT_EQ(1u, args.size());
-    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithRelativeMotion(0, 0)));
-
-    tapGesture = Gesture(kGestureButtonsChange, currentTime, currentTime,
-                         /* down= */ GESTURES_BUTTON_LEFT,
-                         /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
-    args = converter.handleGesture(currentTime, currentTime, currentTime, tapGesture);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
-                                          WithButtonState(0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithButtonState(0))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithButtonState(0)))));
-    ASSERT_THAT(args, Each(VariantWith<NotifyMotionArgs>(WithRelativeMotion(0.f, 0.f))));
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
-    // Click should still produce button press/release events
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
-                         /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, flingGesture);
-    // We don't need to check args here, since it's covered by the FlingTapDown test.
-
-    Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                              /* down= */ GESTURES_BUTTON_LEFT,
-                              /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonDownGesture);
-
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
-                                          WithButtonState(0), WithPressure(0.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithPressure(1.0f)))));
-    ASSERT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(0, 0),
-                                                         WithRelativeMotion(0.f, 0.f),
-                                                         WithToolType(ToolType::FINGER),
-                                                         WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
-                            /* down= */ GESTURES_BUTTON_NONE,
-                            /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
-    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, buttonUpGesture);
-
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
-                                          WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
-                                          WithButtonState(0), WithCoords(0, 0),
-                                          WithRelativeMotion(0.f, 0.f),
-                                          WithToolType(ToolType::FINGER), WithButtonState(0),
-                                          WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
-                                          WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
-                                          WithToolType(ToolType::FINGER), WithButtonState(0),
-                                          WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT))),
-                            VariantWith<NotifyMotionArgs>(
-                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                                          WithCoords(0, 0), WithRelativeMotion(0, 0),
-                                          WithToolType(ToolType::FINGER), WithButtonState(0),
-                                          WithPressure(0.0f),
-                                          WithDisplayId(ADISPLAY_ID_DEFAULT)))));
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
-    // initially disable tap-to-click
-    mReader->getContext()->setPreventingTouchpadTaps(true);
-
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
-
-    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
-    std::list<NotifyArgs> args =
-            converter.handleGesture(ARBITRARY_TIME, READ_TIME, ARBITRARY_TIME, moveGesture);
-    // We don't need to check args here, since it's covered by the Move test.
-
-    // Future taps should be re-enabled
-    ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
-}
-
-TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, KeypressCancelsHoverMove,
-                  REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION_V2)) {
-    const nsecs_t gestureStartTime = 1000;
-    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
-    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
-    converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+    converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
 
     // Start a move gesture at gestureStartTime
     Gesture moveGesture(kGestureMove, gestureStartTime, gestureStartTime, -5, 10);
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
index 8dfa8c8..e87f822 100644
--- a/services/inputflinger/tests/HardwareProperties_test.cpp
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -48,24 +48,19 @@
     static constexpr int32_t EVENTHUB_ID = 1;
 
     void setupValidAxis(int axis, int32_t min, int32_t max, int32_t resolution) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = true;
-                    outAxisInfo->minValue = min;
-                    outAxisInfo->maxValue = max;
-                    outAxisInfo->flat = 0;
-                    outAxisInfo->fuzz = 0;
-                    outAxisInfo->resolution = resolution;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::optional<RawAbsoluteAxisInfo>{{
+                        .minValue = min,
+                        .maxValue = max,
+                        .flat = 0,
+                        .fuzz = 0,
+                        .resolution = resolution,
+                }}));
     }
 
     void setupInvalidAxis(int axis) {
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
-                .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                    outAxisInfo->valid = false;
-                    return -1;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+                .WillRepeatedly(Return(std::nullopt));
     }
 
     void setProperty(int property, bool value) {
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index ff9bd9e..34c81fc 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -81,7 +81,7 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(&event);
+        std::optional<SelfContainedHardwareState> schs = mConverter->processRawEvent(event);
         EXPECT_FALSE(schs.has_value());
     }
 
@@ -93,7 +93,7 @@
         event.type = EV_SYN;
         event.code = SYN_REPORT;
         event.value = 0;
-        return mConverter->processRawEvent(&event);
+        return mConverter->processRawEvent(event);
     }
 
     std::shared_ptr<FakeEventHub> mFakeEventHub;
diff --git a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
index 85e055d..28699b8 100644
--- a/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -18,7 +18,6 @@
 
 #include <NotifyArgsBuilders.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <input/InputEventBuilders.h>
 #include <linux/input.h>
 
@@ -64,7 +63,7 @@
                                        uint32_t sources = TOUCHSCREEN | STYLUS) {
     auto info = InputDeviceInfo();
     info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
-                    "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    "alias", /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     info.addSource(sources);
     return info;
 }
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index dc13fed..6ff5106 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -16,7 +16,9 @@
 
 #include "../dispatcher/InputDispatcher.h"
 #include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
 #include "FakeInputTracingBackend.h"
+#include "FakeWindows.h"
 #include "TestEventMatchers.h"
 
 #include <NotifyArgsBuilders.h>
@@ -33,6 +35,7 @@
 #include <gtest/gtest.h>
 #include <input/BlockingQueue.h>
 #include <input/Input.h>
+#include <input/InputConsumer.h>
 #include <input/PrintTools.h>
 #include <linux/input.h>
 #include <sys/epoll.h>
@@ -56,6 +59,8 @@
 using namespace ftl::flag_operators;
 using testing::AllOf;
 using testing::Not;
+using testing::Pointee;
+using testing::UnorderedElementsAre;
 
 namespace {
 
@@ -67,8 +72,8 @@
 static constexpr int32_t SECOND_DEVICE_ID = 2;
 
 // An arbitrary display id.
-static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
-static constexpr int32_t SECOND_DISPLAY_ID = 1;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECOND_DISPLAY_ID = ui::LogicalDisplayId{1};
 
 // Ensure common actions are interchangeable between keys and motions for convenience.
 static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN);
@@ -105,10 +110,6 @@
 static constexpr int32_t POINTER_2_UP =
         AMOTION_EVENT_ACTION_POINTER_UP | (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 
-// The default pid and uid for windows created on the primary display by the test.
-static constexpr gui::Pid WINDOW_PID{999};
-static constexpr gui::Uid WINDOW_UID{1001};
-
 // The default pid and uid for the windows created on the secondary display by the test.
 static constexpr gui::Pid SECONDARY_WINDOW_PID{1010};
 static constexpr gui::Uid SECONDARY_WINDOW_UID{1012};
@@ -116,24 +117,7 @@
 // An arbitrary pid of the gesture monitor window
 static constexpr gui::Pid MONITOR_PID{2001};
 
-/**
- * If we expect to receive the event, the timeout can be made very long. When the test are running
- * correctly, we will actually never wait until the end of the timeout because the wait will end
- * when the event comes in. Still, this value shouldn't be infinite. During development, a local
- * change may cause the test to fail. This timeout should be short enough to not annoy so that the
- * developer can see the failure quickly (on human scale).
- */
-static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
-/**
- * When no event is expected, we can have a very short timeout. A large value here would slow down
- * the tests. In the unlikely event of system being too slow, the event may still be present but the
- * timeout would complete before it is consumed. This would result in test flakiness. If this
- * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
- * would get noticed and addressed quickly.
- */
-static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
-
-static constexpr int expectedWallpaperFlags =
+static constexpr int EXPECTED_WALLPAPER_FLAGS =
         AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
 
 using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
@@ -144,537 +128,66 @@
 static KeyEvent getTestKeyEvent() {
     KeyEvent event;
 
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
-                     ARBITRARY_TIME, ARBITRARY_TIME);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0,
+                     AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     return event;
 }
 
-// --- FakeInputDispatcherPolicy ---
-
-class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-    struct AnrResult {
-        sp<IBinder> token{};
-        std::optional<gui::Pid> pid{};
-    };
-    /* Stores data about a user-activity-poke event from the dispatcher. */
-    struct UserActivityPokeEvent {
-        nsecs_t eventTime;
-        int32_t eventType;
-        int32_t displayId;
-
-        bool operator==(const UserActivityPokeEvent& rhs) const = default;
-
-        friend std::ostream& operator<<(std::ostream& os, const UserActivityPokeEvent& ev) {
-            os << "UserActivityPokeEvent[time=" << ev.eventTime << ", eventType=" << ev.eventType
-               << ", displayId=" << ev.displayId << "]";
-            return os;
-        }
-    };
-
+/**
+ * Provide a local override for a flag value. The value is restored when the object of this class
+ * goes out of scope.
+ * This class is not intended to be used directly, because its usage is cumbersome.
+ * Instead, a wrapper macro SCOPED_FLAG_OVERRIDE is provided.
+ */
+class ScopedFlagOverride {
 public:
-    FakeInputDispatcherPolicy() = default;
-    virtual ~FakeInputDispatcherPolicy() = default;
-
-    void assertFilterInputEventWasCalled(const NotifyKeyArgs& args) {
-        assertFilterInputEventWasCalledInternal([&args](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), InputEventType::KEY);
-            EXPECT_EQ(event.getDisplayId(), args.displayId);
-
-            const auto& keyEvent = static_cast<const KeyEvent&>(event);
-            EXPECT_EQ(keyEvent.getEventTime(), args.eventTime);
-            EXPECT_EQ(keyEvent.getAction(), args.action);
-        });
+    ScopedFlagOverride(std::function<bool()> read, std::function<void(bool)> write, bool value)
+          : mInitialValue(read()), mWriteValue(write) {
+        mWriteValue(value);
     }
-
-    void assertFilterInputEventWasCalled(const NotifyMotionArgs& args, vec2 point) {
-        assertFilterInputEventWasCalledInternal([&](const InputEvent& event) {
-            ASSERT_EQ(event.getType(), InputEventType::MOTION);
-            EXPECT_EQ(event.getDisplayId(), args.displayId);
-
-            const auto& motionEvent = static_cast<const MotionEvent&>(event);
-            EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
-            EXPECT_EQ(motionEvent.getAction(), args.action);
-            EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
-            EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
-        });
-    }
-
-    void assertFilterInputEventWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_EQ(nullptr, mFilteredEvent);
-    }
-
-    void assertNotifyConfigurationChangedWasCalled(nsecs_t when) {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mConfigurationChangedTime)
-                << "Timed out waiting for configuration changed call";
-        ASSERT_EQ(*mConfigurationChangedTime, when);
-        mConfigurationChangedTime = std::nullopt;
-    }
-
-    void assertNotifySwitchWasCalled(const NotifySwitchArgs& args) {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mLastNotifySwitch);
-        // We do not check id because it is not exposed to the policy
-        EXPECT_EQ(args.eventTime, mLastNotifySwitch->eventTime);
-        EXPECT_EQ(args.policyFlags, mLastNotifySwitch->policyFlags);
-        EXPECT_EQ(args.switchValues, mLastNotifySwitch->switchValues);
-        EXPECT_EQ(args.switchMask, mLastNotifySwitch->switchMask);
-        mLastNotifySwitch = std::nullopt;
-    }
-
-    void assertOnPointerDownEquals(const sp<IBinder>& touchedToken) {
-        std::scoped_lock lock(mLock);
-        ASSERT_EQ(touchedToken, mOnPointerDownToken);
-        mOnPointerDownToken.clear();
-    }
-
-    void assertOnPointerDownWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mOnPointerDownToken == nullptr)
-                << "Expected onPointerDownOutsideFocus to not have been called";
-    }
-
-    // This function must be called soon after the expected ANR timer starts,
-    // because we are also checking how much time has passed.
-    void assertNotifyNoFocusedWindowAnrWasCalled(
-            std::chrono::nanoseconds timeout,
-            const std::shared_ptr<InputApplicationHandle>& expectedApplication) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        std::shared_ptr<InputApplicationHandle> application;
-        ASSERT_NO_FATAL_FAILURE(
-                application = getAnrTokenLockedInterruptible(timeout, mAnrApplications, lock));
-        ASSERT_EQ(expectedApplication, application);
-    }
-
-    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
-                                                 const sp<WindowInfoHandle>& window) {
-        LOG_ALWAYS_FATAL_IF(window == nullptr, "window should not be null");
-        assertNotifyWindowUnresponsiveWasCalled(timeout, window->getToken(),
-                                                window->getInfo()->ownerPid);
-    }
-
-    void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
-                                                 const sp<IBinder>& expectedToken,
-                                                 std::optional<gui::Pid> expectedPid) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result;
-        ASSERT_NO_FATAL_FAILURE(result =
-                                        getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
-        ASSERT_EQ(expectedToken, result.token);
-        ASSERT_EQ(expectedPid, result.pid);
-    }
-
-    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
-    sp<IBinder> getUnresponsiveWindowToken(std::chrono::nanoseconds timeout) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result = getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock);
-        const auto& [token, _] = result;
-        return token;
-    }
-
-    void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
-                                               std::optional<gui::Pid> expectedPid) {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result;
-        ASSERT_NO_FATAL_FAILURE(
-                result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
-        ASSERT_EQ(expectedToken, result.token);
-        ASSERT_EQ(expectedPid, result.pid);
-    }
-
-    /** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
-    sp<IBinder> getResponsiveWindowToken() {
-        std::unique_lock lock(mLock);
-        android::base::ScopedLockAssertion assumeLocked(mLock);
-        AnrResult result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock);
-        const auto& [token, _] = result;
-        return token;
-    }
-
-    void assertNotifyAnrWasNotCalled() {
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mAnrApplications.empty());
-        ASSERT_TRUE(mAnrWindows.empty());
-        ASSERT_TRUE(mResponsiveWindows.empty())
-                << "ANR was not called, but please also consume the 'connection is responsive' "
-                   "signal";
-    }
-
-    PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        if (!mPointerCaptureChangedCondition.wait_for(lock, 100ms,
-                                                      [this, enabled]() REQUIRES(mLock) {
-                                                          return mPointerCaptureRequest->enable ==
-                                                                  enabled;
-                                                      })) {
-            ADD_FAILURE() << "Timed out waiting for setPointerCapture(" << enabled
-                          << ") to be called.";
-            return {};
-        }
-        auto request = *mPointerCaptureRequest;
-        mPointerCaptureRequest.reset();
-        return request;
-    }
-
-    void assertSetPointerCaptureNotCalled() {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        if (mPointerCaptureChangedCondition.wait_for(lock, 100ms) != std::cv_status::timeout) {
-            FAIL() << "Expected setPointerCapture(request) to not be called, but was called. "
-                      "enabled = "
-                   << std::to_string(mPointerCaptureRequest->enable);
-        }
-        mPointerCaptureRequest.reset();
-    }
-
-    void assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
-                                const sp<IBinder>& targetToken) {
-        dispatcher.waitForIdle();
-        std::scoped_lock lock(mLock);
-        ASSERT_TRUE(mNotifyDropWindowWasCalled);
-        ASSERT_EQ(targetToken, mDropTargetWindowToken);
-        mNotifyDropWindowWasCalled = false;
-    }
-
-    void assertNotifyInputChannelBrokenWasCalled(const sp<IBinder>& token) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<sp<IBinder>> receivedToken =
-                getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
-                                                      mNotifyInputChannelBroken);
-        ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
-        ASSERT_EQ(token, *receivedToken);
-    }
-
-    /**
-     * Set policy timeout. A value of zero means next key will not be intercepted.
-     */
-    void setInterceptKeyTimeout(std::chrono::milliseconds timeout) {
-        mInterceptKeyTimeout = timeout;
-    }
-
-    std::chrono::nanoseconds getKeyWaitingForEventsTimeout() override { return 500ms; }
-
-    void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = timeout; }
-
-    void assertUserActivityNotPoked() {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        std::optional<UserActivityPokeEvent> pokeEvent =
-                getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
-                                                      mNotifyUserActivity);
-
-        ASSERT_FALSE(pokeEvent) << "Expected user activity not to have been poked";
-    }
-
-    /**
-     * Asserts that a user activity poke has happened. The earliest recorded poke event will be
-     * cleared after this call.
-     *
-     * If an expected UserActivityPokeEvent is provided, asserts that the given event is the
-     * earliest recorded poke event.
-     */
-    void assertUserActivityPoked(std::optional<UserActivityPokeEvent> expectedPokeEvent = {}) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-
-        std::optional<UserActivityPokeEvent> pokeEvent =
-                getItemFromStorageLockedInterruptible(500ms, mUserActivityPokeEvents, lock,
-                                                      mNotifyUserActivity);
-        ASSERT_TRUE(pokeEvent) << "Expected a user poke event";
-
-        if (expectedPokeEvent) {
-            ASSERT_EQ(expectedPokeEvent, *pokeEvent);
-        }
-    }
-
-    void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids) {
-        ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
-    }
-
-    void assertNotifyDeviceInteractionWasNotCalled() {
-        ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
-    }
-
-    void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
-        std::scoped_lock lock(mLock);
-        mUnhandledKeyHandler = handler;
-    }
-
-    void assertUnhandledKeyReported(int32_t keycode) {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<int32_t> unhandledKeycode =
-                getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
-                                                      mNotifyUnhandledKey);
-        ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
-        ASSERT_EQ(unhandledKeycode, keycode);
-    }
-
-    void assertUnhandledKeyNotReported() {
-        std::unique_lock lock(mLock);
-        base::ScopedLockAssertion assumeLocked(mLock);
-        std::optional<int32_t> unhandledKeycode =
-                getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
-                                                      mNotifyUnhandledKey);
-        ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
-    }
+    ~ScopedFlagOverride() { mWriteValue(mInitialValue); }
 
 private:
-    std::mutex mLock;
-    std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
-    std::optional<nsecs_t> mConfigurationChangedTime GUARDED_BY(mLock);
-    sp<IBinder> mOnPointerDownToken GUARDED_BY(mLock);
-    std::optional<NotifySwitchArgs> mLastNotifySwitch GUARDED_BY(mLock);
-
-    std::condition_variable mPointerCaptureChangedCondition;
-
-    std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
-
-    // ANR handling
-    std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
-    std::queue<AnrResult> mAnrWindows GUARDED_BY(mLock);
-    std::queue<AnrResult> mResponsiveWindows GUARDED_BY(mLock);
-    std::condition_variable mNotifyAnr;
-    std::queue<sp<IBinder>> mBrokenInputChannels GUARDED_BY(mLock);
-    std::condition_variable mNotifyInputChannelBroken;
-
-    sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
-    bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
-
-    std::condition_variable mNotifyUserActivity;
-    std::queue<UserActivityPokeEvent> mUserActivityPokeEvents;
-
-    std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
-
-    std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
-
-    BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
-
-    std::condition_variable mNotifyUnhandledKey;
-    std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
-    std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
-
-    // All three ANR-related callbacks behave the same way, so we use this generic function to wait
-    // for a specific container to become non-empty. When the container is non-empty, return the
-    // first entry from the container and erase it.
-    template <class T>
-    T getAnrTokenLockedInterruptible(std::chrono::nanoseconds timeout, std::queue<T>& storage,
-                                     std::unique_lock<std::mutex>& lock) REQUIRES(mLock) {
-        // If there is an ANR, Dispatcher won't be idle because there are still events
-        // in the waitQueue that we need to check on. So we can't wait for dispatcher to be idle
-        // before checking if ANR was called.
-        // Since dispatcher is not guaranteed to call notifyNoFocusedWindowAnr right away, we need
-        // to provide it some time to act. 100ms seems reasonable.
-        std::chrono::duration timeToWait = timeout + 100ms; // provide some slack
-        const std::chrono::time_point start = std::chrono::steady_clock::now();
-        std::optional<T> token =
-                getItemFromStorageLockedInterruptible(timeToWait, storage, lock, mNotifyAnr);
-        if (!token.has_value()) {
-            ADD_FAILURE() << "Did not receive the ANR callback";
-            return {};
-        }
-
-        const std::chrono::duration waited = std::chrono::steady_clock::now() - start;
-        // Ensure that the ANR didn't get raised too early. We can't be too strict here because
-        // the dispatcher started counting before this function was called
-        if (std::chrono::abs(timeout - waited) > 100ms) {
-            ADD_FAILURE() << "ANR was raised too early or too late. Expected "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(timeout).count()
-                          << "ms, but waited "
-                          << std::chrono::duration_cast<std::chrono::milliseconds>(waited).count()
-                          << "ms instead";
-        }
-        return *token;
-    }
-
-    template <class T>
-    std::optional<T> getItemFromStorageLockedInterruptible(std::chrono::nanoseconds timeout,
-                                                           std::queue<T>& storage,
-                                                           std::unique_lock<std::mutex>& lock,
-                                                           std::condition_variable& condition)
-            REQUIRES(mLock) {
-        condition.wait_for(lock, timeout,
-                           [&storage]() REQUIRES(mLock) { return !storage.empty(); });
-        if (storage.empty()) {
-            return std::nullopt;
-        }
-        T item = storage.front();
-        storage.pop();
-        return std::make_optional(item);
-    }
-
-    void notifyConfigurationChanged(nsecs_t when) override {
-        std::scoped_lock lock(mLock);
-        mConfigurationChangedTime = when;
-    }
-
-    void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
-                                  const std::string&) override {
-        std::scoped_lock lock(mLock);
-        mAnrWindows.push({connectionToken, pid});
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyWindowResponsive(const sp<IBinder>& connectionToken,
-                                std::optional<gui::Pid> pid) override {
-        std::scoped_lock lock(mLock);
-        mResponsiveWindows.push({connectionToken, pid});
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyNoFocusedWindowAnr(
-            const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
-        std::scoped_lock lock(mLock);
-        mAnrApplications.push(applicationHandle);
-        mNotifyAnr.notify_all();
-    }
-
-    void notifyInputChannelBroken(const sp<IBinder>& connectionToken) override {
-        std::scoped_lock lock(mLock);
-        mBrokenInputChannels.push(connectionToken);
-        mNotifyInputChannelBroken.notify_all();
-    }
-
-    void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
-    void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
-                           InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
-                           const std::vector<float>& values) override {}
-
-    void notifySensorAccuracy(int deviceId, InputDeviceSensorType sensorType,
-                              InputDeviceSensorAccuracy accuracy) override {}
-
-    void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
-    bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
-        std::scoped_lock lock(mLock);
-        switch (inputEvent.getType()) {
-            case InputEventType::KEY: {
-                const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
-                mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
-                break;
-            }
-
-            case InputEventType::MOTION: {
-                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
-                mFilteredEvent = std::make_unique<MotionEvent>(motionEvent);
-                break;
-            }
-            default: {
-                ADD_FAILURE() << "Should only filter keys or motions";
-                break;
-            }
-        }
-        return true;
-    }
-
-    void interceptKeyBeforeQueueing(const KeyEvent& inputEvent, uint32_t&) override {
-        if (inputEvent.getAction() == AKEY_EVENT_ACTION_UP) {
-            // Clear intercept state when we handled the event.
-            mInterceptKeyTimeout = 0ms;
-        }
-    }
-
-    void interceptMotionBeforeQueueing(int32_t, uint32_t, int32_t, nsecs_t, uint32_t&) override {}
-
-    nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
-        nsecs_t delay = std::chrono::nanoseconds(mInterceptKeyTimeout).count();
-        // Clear intercept state so we could dispatch the event in next wake.
-        mInterceptKeyTimeout = 0ms;
-        return delay;
-    }
-
-    std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
-                                                 uint32_t) override {
-        std::scoped_lock lock(mLock);
-        mReportedUnhandledKeycodes.emplace(event.getKeyCode());
-        mNotifyUnhandledKey.notify_all();
-        return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
-    }
-
-    void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
-                      uint32_t policyFlags) override {
-        std::scoped_lock lock(mLock);
-        /** We simply reconstruct NotifySwitchArgs in policy because InputDispatcher is
-         * essentially a passthrough for notifySwitch.
-         */
-        mLastNotifySwitch =
-                NotifySwitchArgs(InputEvent::nextId(), when, policyFlags, switchValues, switchMask);
-    }
-
-    void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override {
-        std::scoped_lock lock(mLock);
-        mNotifyUserActivity.notify_all();
-        mUserActivityPokeEvents.push({eventTime, eventType, displayId});
-    }
-
-    bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override {
-        return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
-    }
-
-    void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
-        std::scoped_lock lock(mLock);
-        mOnPointerDownToken = newToken;
-    }
-
-    void setPointerCapture(const PointerCaptureRequest& request) override {
-        std::scoped_lock lock(mLock);
-        mPointerCaptureRequest = {request};
-        mPointerCaptureChangedCondition.notify_all();
-    }
-
-    void notifyDropWindow(const sp<IBinder>& token, float x, float y) override {
-        std::scoped_lock lock(mLock);
-        mNotifyDropWindowWasCalled = true;
-        mDropTargetWindowToken = token;
-    }
-
-    void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
-                                 const std::set<gui::Uid>& uids) override {
-        ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
-    }
-
-    void assertFilterInputEventWasCalledInternal(
-            const std::function<void(const InputEvent&)>& verify) {
-        std::scoped_lock lock(mLock);
-        ASSERT_NE(nullptr, mFilteredEvent) << "Expected filterInputEvent() to have been called.";
-        verify(*mFilteredEvent);
-        mFilteredEvent = nullptr;
-    }
+    const bool mInitialValue;
+    std::function<void(bool)> mWriteValue;
 };
+
+typedef bool (*readFlagValueFunction)();
+typedef void (*writeFlagValueFunction)(bool);
+
+/**
+ * Use this macro to locally override a flag value.
+ * Example usage:
+ *    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+ * Note: this works by creating a local variable in your current scope. Don't call this twice for
+ * the same flag, because the variable names will clash!
+ */
+#define SCOPED_FLAG_OVERRIDE(NAME, VALUE)                                  \
+    readFlagValueFunction read##NAME = com::android::input::flags::NAME;   \
+    writeFlagValueFunction write##NAME = com::android::input::flags::NAME; \
+    ScopedFlagOverride override##NAME(read##NAME, write##NAME, (VALUE))
+
 } // namespace
 
 // --- InputDispatcherTest ---
 
-// The trace is a global variable for now, to avoid having to pass it into all of the
-// FakeWindowHandles created throughout the tests.
-// TODO(b/210460522): Update the tests to avoid the need to have the trace be a global variable.
-static std::shared_ptr<VerifyingTrace> gVerifyingTrace = std::make_shared<VerifyingTrace>();
-
 class InputDispatcherTest : public testing::Test {
 protected:
     std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
     std::unique_ptr<InputDispatcher> mDispatcher;
+    std::shared_ptr<VerifyingTrace> mVerifyingTrace;
 
     void SetUp() override {
-        gVerifyingTrace->reset();
+        mVerifyingTrace = std::make_shared<VerifyingTrace>();
+        FakeWindowHandle::sOnEventReceivedCallback = [this](const auto& _1, const auto& _2) {
+            handleEventReceivedByWindow(_1, _2);
+        };
+
         mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
         mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy,
                                                         std::make_unique<FakeInputTracingBackend>(
-                                                                gVerifyingTrace));
+                                                                mVerifyingTrace));
 
         mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
         // Start InputDispatcher thread
@@ -682,12 +195,35 @@
     }
 
     void TearDown() override {
-        ASSERT_NO_FATAL_FAILURE(gVerifyingTrace->verifyExpectedEventsTraced());
+        ASSERT_NO_FATAL_FAILURE(mVerifyingTrace->verifyExpectedEventsTraced());
+        FakeWindowHandle::sOnEventReceivedCallback = nullptr;
+
         ASSERT_EQ(OK, mDispatcher->stop());
         mFakePolicy.reset();
         mDispatcher.reset();
     }
 
+    void handleEventReceivedByWindow(const std::unique_ptr<InputEvent>& event,
+                                     const gui::WindowInfo& info) {
+        if (!event) {
+            return;
+        }
+
+        switch (event->getType()) {
+            case InputEventType::KEY: {
+                mVerifyingTrace->expectKeyDispatchTraced(static_cast<KeyEvent&>(*event), info.id);
+                break;
+            }
+            case InputEventType::MOTION: {
+                mVerifyingTrace->expectMotionDispatchTraced(static_cast<MotionEvent&>(*event),
+                                                            info.id);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
     /**
      * Used for debugging when writing the test
      */
@@ -707,7 +243,7 @@
         request.token = window->getToken();
         request.windowName = window->getName();
         request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
-        request.displayId = window->getInfo()->displayId;
+        request.displayId = window->getInfo()->displayId.val();
         mDispatcher->setFocusedWindow(request);
     }
 };
@@ -716,8 +252,8 @@
     KeyEvent event;
 
     // Rejects undefined key actions.
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC,
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC,
                      /*action=*/-1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
                      ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
@@ -726,9 +262,9 @@
             << "Should reject key events with undefined action.";
 
     // Rejects ACTION_MULTIPLE since it is not supported despite being defined in the API.
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0,
-                     ARBITRARY_TIME, ARBITRARY_TIME);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_MULTIPLE, 0,
+                     AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME, ARBITRARY_TIME);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
                                             0ms, 0))
@@ -875,16 +411,6 @@
             << "Should reject motion events with duplicate pointer ids.";
 }
 
-/* Test InputDispatcher for notifyConfigurationChanged and notifySwitch events */
-
-TEST_F(InputDispatcherTest, NotifyConfigurationChanged_CallsPolicy) {
-    constexpr nsecs_t eventTime = 20;
-    mDispatcher->notifyConfigurationChanged({/*id=*/10, eventTime});
-    ASSERT_TRUE(mDispatcher->waitForIdle());
-
-    mFakePolicy->assertNotifyConfigurationChangedWasCalled(eventTime);
-}
-
 TEST_F(InputDispatcherTest, NotifySwitch_CallsPolicy) {
     NotifySwitchArgs args(InputEvent::nextId(), /*eventTime=*/20, /*policyFlags=*/0,
                           /*switchValues=*/1,
@@ -899,627 +425,16 @@
 namespace {
 
 static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms;
-// Default input dispatching timeout if there is no focused application or paused window
-// from which to determine an appropriate dispatching timeout.
-static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
-        android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
-        android::base::HwTimeoutMultiplier());
-
-class FakeInputReceiver {
-public:
-    explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name)
-          : mConsumer(std::move(clientChannel)), mName(name) {}
-
-    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = false) {
-        auto [consumeSeq, event] = receiveEvent(timeout);
-        if (!consumeSeq) {
-            return nullptr;
-        }
-        finishEvent(*consumeSeq, handled);
-        return std::move(event);
-    }
-
-    /**
-     * Receive an event without acknowledging it.
-     * Return the sequence number that could later be used to send finished signal.
-     */
-    std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent(
-            std::chrono::milliseconds timeout) {
-        uint32_t consumeSeq;
-        std::unique_ptr<InputEvent> event;
-
-        std::chrono::time_point start = std::chrono::steady_clock::now();
-        status_t status = WOULD_BLOCK;
-        while (status == WOULD_BLOCK) {
-            InputEvent* rawEventPtr = nullptr;
-            status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
-                                       &rawEventPtr);
-            event = std::unique_ptr<InputEvent>(rawEventPtr);
-            std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
-            if (elapsed > timeout) {
-                break;
-            }
-        }
-
-        if (status == WOULD_BLOCK) {
-            // Just means there's no event available.
-            return std::make_pair(std::nullopt, nullptr);
-        }
-
-        if (status != OK) {
-            ADD_FAILURE() << mName.c_str() << ": consumer consume should return OK.";
-            return std::make_pair(std::nullopt, nullptr);
-        }
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consumed correctly, but received NULL event from consumer";
-        }
-        return std::make_pair(consumeSeq, std::move(event));
-    }
-
-    /**
-     * To be used together with "receiveEvent" to complete the consumption of an event.
-     */
-    void finishEvent(uint32_t consumeSeq, bool handled = true) {
-        const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
-        ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
-    }
-
-    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
-        ASSERT_EQ(OK, status);
-    }
-
-    void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
-                      std::optional<int32_t> expectedDisplayId,
-                      std::optional<int32_t> expectedFlags) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL event.";
-        ASSERT_EQ(expectedEventType, event->getType())
-                << mName.c_str() << " expected " << ftl::enum_string(expectedEventType)
-                << " event, got " << *event;
-
-        if (expectedDisplayId.has_value()) {
-            EXPECT_EQ(expectedDisplayId, event->getDisplayId());
-        }
-
-        switch (expectedEventType) {
-            case InputEventType::KEY: {
-                const KeyEvent& keyEvent = static_cast<const KeyEvent&>(*event);
-                ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
-                if (expectedFlags.has_value()) {
-                    EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
-                }
-                break;
-            }
-            case InputEventType::MOTION: {
-                const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
-                if (expectedFlags.has_value()) {
-                    EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
-                }
-                break;
-            }
-            case InputEventType::FOCUS: {
-                FAIL() << "Use 'consumeFocusEvent' for FOCUS events";
-            }
-            case InputEventType::CAPTURE: {
-                FAIL() << "Use 'consumeCaptureEvent' for CAPTURE events";
-            }
-            case InputEventType::TOUCH_MODE: {
-                FAIL() << "Use 'consumeTouchModeEvent' for TOUCH_MODE events";
-            }
-            case InputEventType::DRAG: {
-                FAIL() << "Use 'consumeDragEvent' for DRAG events";
-            }
-        }
-    }
-
-    std::unique_ptr<MotionEvent> consumeMotion() {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-
-        if (event == nullptr) {
-            ADD_FAILURE() << mName << ": expected a MotionEvent, but didn't get one.";
-            return nullptr;
-        }
-
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << mName << " expected a MotionEvent, got " << *event;
-            return nullptr;
-        }
-        return std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
-    }
-
-    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
-        std::unique_ptr<MotionEvent> motionEvent = consumeMotion();
-        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event, but expected " << matcher;
-        ASSERT_THAT(*motionEvent, matcher);
-    }
-
-    void consumeFocusEvent(bool hasFocus, bool inTouchMode) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL 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.";
-
-        FocusEvent& focusEvent = static_cast<FocusEvent&>(*event);
-        EXPECT_EQ(hasFocus, focusEvent.getHasFocus());
-    }
-
-    void consumeCaptureEvent(bool hasCapture) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL 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.";
-
-        const auto& captureEvent = static_cast<const CaptureEvent&>(*event);
-        EXPECT_EQ(hasCapture, captureEvent.getPointerCaptureEnabled());
-    }
-
-    void consumeDragEvent(bool isExiting, float x, float y) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL 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.";
-
-        const auto& dragEvent = static_cast<const DragEvent&>(*event);
-        EXPECT_EQ(isExiting, dragEvent.isExiting());
-        EXPECT_EQ(x, dragEvent.getX());
-        EXPECT_EQ(y, dragEvent.getY());
-    }
-
-    void consumeTouchModeEvent(bool inTouchMode) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        ASSERT_NE(nullptr, event) << mName.c_str()
-                                  << ": consumer should have returned non-NULL 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.";
-        const auto& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
-        EXPECT_EQ(inTouchMode, touchModeEvent.isInTouchMode());
-    }
-
-    void assertNoEvents(std::chrono::milliseconds timeout) {
-        std::unique_ptr<InputEvent> event = consume(timeout);
-        if (event == nullptr) {
-            return;
-        }
-        if (event->getType() == InputEventType::KEY) {
-            KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
-            ADD_FAILURE() << "Received key event " << keyEvent;
-        } else if (event->getType() == InputEventType::MOTION) {
-            MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
-            ADD_FAILURE() << "Received motion event " << motionEvent;
-        } 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() == InputEventType::CAPTURE) {
-            const auto& captureEvent = static_cast<CaptureEvent&>(*event);
-            ADD_FAILURE() << "Received capture event, pointerCaptureEnabled = "
-                          << (captureEvent.getPointerCaptureEnabled() ? "true" : "false");
-        } 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");
-        }
-        FAIL() << mName.c_str()
-               << ": should not have received any events, so consume() should return NULL";
-    }
-
-    sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
-
-    int getChannelFd() { return mConsumer.getChannel()->getFd(); }
-
-private:
-    InputConsumer mConsumer;
-    DynamicInputEventFactory mEventFactory;
-
-    std::string mName;
-};
-
-class FakeWindowHandle : public WindowInfoHandle {
-public:
-    static const int32_t WIDTH = 600;
-    static const int32_t HEIGHT = 800;
-
-    FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
-                     const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
-                     int32_t displayId, bool createInputChannel = true)
-          : mName(name) {
-        sp<IBinder> token;
-        if (createInputChannel) {
-            base::Result<std::unique_ptr<InputChannel>> channel =
-                    dispatcher->createInputChannel(name);
-            token = (*channel)->getConnectionToken();
-            mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
-        }
-
-        inputApplicationHandle->updateInfo();
-        mInfo.applicationInfo = *inputApplicationHandle->getInfo();
-
-        mInfo.token = token;
-        mInfo.id = sId++;
-        mInfo.name = name;
-        mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
-        mInfo.alpha = 1.0;
-        mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
-        mInfo.transform.set(0, 0);
-        mInfo.globalScaleFactor = 1.0;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
-        mInfo.ownerPid = WINDOW_PID;
-        mInfo.ownerUid = WINDOW_UID;
-        mInfo.displayId = displayId;
-        mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
-    }
-
-    sp<FakeWindowHandle> clone(int32_t displayId) {
-        sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
-        handle->mInfo = mInfo;
-        handle->mInfo.displayId = displayId;
-        handle->mInfo.id = sId++;
-        handle->mInputReceiver = mInputReceiver;
-        return handle;
-    }
-
-    void setTouchable(bool touchable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
-    }
-
-    void setFocusable(bool focusable) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
-    }
-
-    void setVisible(bool visible) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
-    }
-
-    void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
-        mInfo.dispatchingTimeout = timeout;
-    }
-
-    void setPaused(bool paused) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
-    }
-
-    void setPreventSplitting(bool preventSplitting) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
-    }
-
-    void setSlippery(bool slippery) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
-    }
-
-    void setWatchOutsideTouch(bool watchOutside) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
-    }
-
-    void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
-
-    void setInterceptsStylus(bool interceptsStylus) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
-    }
-
-    void setDropInput(bool dropInput) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
-    }
-
-    void setDropInputIfObscured(bool dropInputIfObscured) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
-    }
-
-    void setNoInputChannel(bool noInputChannel) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::NO_INPUT_CHANNEL, noInputChannel);
-    }
-
-    void setDisableUserActivity(bool disableUserActivity) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY, disableUserActivity);
-    }
-
-    void setGlobalStylusBlocksTouch(bool shouldGlobalStylusBlockTouch) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH,
-                             shouldGlobalStylusBlockTouch);
-    }
-
-    void setAlpha(float alpha) { mInfo.alpha = alpha; }
-
-    void setTouchOcclusionMode(TouchOcclusionMode mode) { mInfo.touchOcclusionMode = mode; }
-
-    void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
-
-    void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
-        mInfo.frame = frame;
-        mInfo.touchableRegion.clear();
-        mInfo.addTouchableRegion(frame);
-
-        const Rect logicalDisplayFrame = displayTransform.transform(frame);
-        ui::Transform translate;
-        translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
-        mInfo.transform = translate * displayTransform;
-    }
-
-    void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
-
-    void setIsWallpaper(bool isWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
-    }
-
-    void setDupTouchToWallpaper(bool hasWallpaper) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
-    }
-
-    void setTrustedOverlay(bool trustedOverlay) {
-        mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
-    }
-
-    void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
-        mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
-    }
-
-    void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
-
-    void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
-
-    std::unique_ptr<KeyEvent> consumeKey(bool handled = true) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
-        if (event == nullptr) {
-            ADD_FAILURE() << "No event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::KEY) {
-            ADD_FAILURE() << "Instead of key event, got " << event;
-            return nullptr;
-        }
-        return std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event.release()));
-    }
-
-    void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
-        std::unique_ptr<KeyEvent> keyEvent = consumeKey();
-        ASSERT_NE(nullptr, keyEvent);
-        ASSERT_THAT(*keyEvent, matcher);
-    }
-
-    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeKeyEvent(AllOf(WithKeyAction(ACTION_DOWN), WithDisplayId(expectedDisplayId),
-                              WithFlags(expectedFlags)));
-    }
-
-    void consumeKeyUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
-        consumeKeyEvent(AllOf(WithKeyAction(ACTION_UP), WithDisplayId(expectedDisplayId),
-                              WithFlags(expectedFlags)));
-    }
-
-    void consumeMotionCancel(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                             int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(expectedDisplayId),
-                                 WithFlags(expectedFlags | AMOTION_EVENT_FLAG_CANCELED)));
-    }
-
-    void consumeMotionMove(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                           int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
-                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
-    }
-
-    void consumeMotionDown(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                           int32_t expectedFlags = 0) {
-        consumeAnyMotionDown(expectedDisplayId, expectedFlags);
-    }
-
-    void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
-                              std::optional<int32_t> expectedFlags = std::nullopt) {
-        consumeMotionEvent(
-                AllOf(WithMotionAction(ACTION_DOWN),
-                      testing::Conditional(expectedDisplayId.has_value(),
-                                           WithDisplayId(*expectedDisplayId), testing::_),
-                      testing::Conditional(expectedFlags.has_value(), WithFlags(*expectedFlags),
-                                           testing::_)));
-    }
-
-    void consumeMotionPointerDown(int32_t pointerIdx,
-                                  int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                  int32_t expectedFlags = 0) {
-        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
-                                 WithFlags(expectedFlags)));
-    }
-
-    inline void consumeMotionPointerDown(int32_t pointerIdx,
-                                         const ::testing::Matcher<MotionEvent>& matcher) {
-        const int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
-    }
-
-    void consumeMotionPointerUp(int32_t pointerIdx, int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                                int32_t expectedFlags = 0) {
-        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeMotionEvent(AllOf(WithMotionAction(action), WithDisplayId(expectedDisplayId),
-                                 WithFlags(expectedFlags)));
-    }
-
-    inline void consumeMotionPointerUp(int32_t pointerIdx,
-                                       const ::testing::Matcher<MotionEvent>& matcher) {
-        const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP |
-                (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        consumeMotionEvent(testing::AllOf(WithMotionAction(action), matcher));
-    }
-
-    void consumeMotionUp(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                         int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDisplayId(expectedDisplayId),
-                                 WithFlags(expectedFlags)));
-    }
-
-    void consumeMotionOutside(int32_t expectedDisplayId = ADISPLAY_ID_DEFAULT,
-                              int32_t expectedFlags = 0) {
-        consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE),
-                                 WithDisplayId(expectedDisplayId), WithFlags(expectedFlags)));
-    }
-
-    void consumeMotionOutsideWithZeroedCoords() {
-        consumeMotionEvent(
-                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_OUTSIDE), WithRawCoords(0, 0)));
-    }
-
-    void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeFocusEvent(hasFocus, inTouchMode);
-    }
-
-    void consumeCaptureEvent(bool hasCapture) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeCaptureEvent(hasCapture);
-    }
-
-    std::unique_ptr<MotionEvent> consumeMotionEvent(
-            const ::testing::Matcher<MotionEvent>& matcher = testing::_) {
-        std::unique_ptr<InputEvent> event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        if (event == nullptr) {
-            ADD_FAILURE() << "No event";
-            return nullptr;
-        }
-        if (event->getType() != InputEventType::MOTION) {
-            ADD_FAILURE() << "Instead of motion event, got " << *event;
-            return nullptr;
-        }
-        std::unique_ptr<MotionEvent> motionEvent =
-                std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event.release()));
-        EXPECT_THAT(*motionEvent, matcher);
-        return motionEvent;
-    }
-
-    void consumeDragEvent(bool isExiting, float x, float y) {
-        mInputReceiver->consumeDragEvent(isExiting, x, y);
-    }
-
-    void consumeTouchModeEvent(bool inTouchMode) {
-        ASSERT_NE(mInputReceiver, nullptr)
-                << "Cannot consume events from a window with no receiver";
-        mInputReceiver->consumeTouchModeEvent(inTouchMode);
-    }
-
-    std::pair<std::optional<uint32_t>, std::unique_ptr<InputEvent>> receiveEvent() {
-        return receive();
-    }
-
-    void finishEvent(uint32_t sequenceNum) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
-        mInputReceiver->finishEvent(sequenceNum);
-    }
-
-    void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
-        ASSERT_NE(mInputReceiver, nullptr) << "Invalid receive event on window with no receiver";
-        mInputReceiver->sendTimeline(inputEventId, timeline);
-    }
-
-    void assertNoEvents(std::chrono::milliseconds timeout = CONSUME_TIMEOUT_NO_EVENT_EXPECTED) {
-        if (mInputReceiver == nullptr &&
-            mInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
-            return; // Can't receive events if the window does not have input channel
-        }
-        ASSERT_NE(nullptr, mInputReceiver)
-                << "Window without InputReceiver must specify feature NO_INPUT_CHANNEL";
-        mInputReceiver->assertNoEvents(timeout);
-    }
-
-    sp<IBinder> getToken() { return mInfo.token; }
-
-    const std::string& getName() { return mName; }
-
-    void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) {
-        mInfo.ownerPid = ownerPid;
-        mInfo.ownerUid = ownerUid;
-    }
-
-    gui::Pid getPid() const { return mInfo.ownerPid; }
-
-    void destroyReceiver() { mInputReceiver = nullptr; }
-
-    int getChannelFd() { return mInputReceiver->getChannelFd(); }
-
-    // FakeWindowHandle uses this consume method to ensure received events are added to the trace.
-    std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout, bool handled = true) {
-        if (mInputReceiver == nullptr) {
-            LOG(FATAL) << "Cannot consume event from a window with no input event receiver";
-        }
-        std::unique_ptr<InputEvent> event = mInputReceiver->consume(timeout, handled);
-        if (event == nullptr) {
-            ADD_FAILURE() << "Consume failed: no event";
-        }
-        expectReceivedEventTraced(event);
-        return event;
-    }
-
-private:
-    FakeWindowHandle(std::string name) : mName(name){};
-    const std::string mName;
-    std::shared_ptr<FakeInputReceiver> mInputReceiver;
-    static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
-    friend class sp<FakeWindowHandle>;
-
-    // FakeWindowHandle uses this receive method to ensure received events are added to the trace.
-    std::pair<std::optional<uint32_t /*seq*/>, std::unique_ptr<InputEvent>> receive() {
-        if (mInputReceiver == nullptr) {
-            ADD_FAILURE() << "Invalid receive event on window with no receiver";
-            return std::make_pair(std::nullopt, nullptr);
-        }
-        auto out = mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
-        const auto& [_, event] = out;
-        expectReceivedEventTraced(event);
-        return std::move(out);
-    }
-
-    void expectReceivedEventTraced(const std::unique_ptr<InputEvent>& event) {
-        if (!event) {
-            return;
-        }
-
-        switch (event->getType()) {
-            case InputEventType::KEY: {
-                gVerifyingTrace->expectKeyDispatchTraced(static_cast<KeyEvent&>(*event), mInfo.id);
-                break;
-            }
-            case InputEventType::MOTION: {
-                gVerifyingTrace->expectMotionDispatchTraced(static_cast<MotionEvent&>(*event),
-                                                            mInfo.id);
-                break;
-            }
-            default:
-                break;
-        }
-    }
-};
-
-std::atomic<int32_t> FakeWindowHandle::sId{1};
 
 class FakeMonitorReceiver {
 public:
-    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+    FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name,
+                        ui::LogicalDisplayId displayId)
           : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {}
 
     sp<IBinder> getToken() { return mInputReceiver.getToken(); }
 
-    void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeKeyDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
                                     expectedFlags);
     }
@@ -1531,22 +446,22 @@
 
     void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); }
 
-    void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionDown(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionMove(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionUp(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
                                     expectedDisplayId, expectedFlags);
     }
 
-    void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+    void consumeMotionCancel(ui::LogicalDisplayId expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver.consumeMotionEvent(
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_CANCEL),
                       WithDisplayId(expectedDisplayId),
@@ -1556,7 +471,7 @@
     void consumeMotionPointerDown(int32_t pointerIdx) {
         int32_t action = AMOTION_EVENT_ACTION_POINTER_DOWN |
                 (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
-        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+        mInputReceiver.consumeEvent(InputEventType::MOTION, action, ui::LogicalDisplayId::DEFAULT,
                                     /*expectedFlags=*/0);
     }
 
@@ -1574,7 +489,7 @@
 
 static InputEventInjectionResult injectKey(
         InputDispatcher& dispatcher, int32_t action, int32_t repeatCount,
-        int32_t displayId = ADISPLAY_ID_NONE,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID,
         InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
         std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
         bool allowKeyRepeat = true, std::optional<gui::Uid> targetUid = {},
@@ -1596,30 +511,34 @@
 
 static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) {
     InputEventInjectionResult result =
-            injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_NONE,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
+            injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::INVALID, InputEventInjectionSync::WAIT_FOR_RESULT,
+                      CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
     if (result != InputEventInjectionResult::TIMED_OUT) {
         FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result);
     }
 }
 
-static InputEventInjectionResult injectKeyDown(InputDispatcher& dispatcher,
-                                               int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDown(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId);
 }
 
 // Inject a down event that has key repeat disabled. This allows InputDispatcher to idle without
 // sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it
 // has to be woken up to process the repeating key.
-static InputEventInjectionResult injectKeyDownNoRepeat(InputDispatcher& dispatcher,
-                                                       int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyDownNoRepeat(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, displayId,
                      InputEventInjectionSync::WAIT_FOR_RESULT, INJECT_EVENT_TIMEOUT,
                      /*allowKeyRepeat=*/false);
 }
 
-static InputEventInjectionResult injectKeyUp(InputDispatcher& dispatcher,
-                                             int32_t displayId = ADISPLAY_ID_NONE) {
+static InputEventInjectionResult injectKeyUp(
+        InputDispatcher& dispatcher,
+        ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     return injectKey(dispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, displayId);
 }
 
@@ -1633,7 +552,7 @@
 }
 
 static InputEventInjectionResult injectMotionEvent(
-        InputDispatcher& dispatcher, int32_t action, int32_t source, int32_t displayId,
+        InputDispatcher& dispatcher, int32_t action, int32_t source, ui::LogicalDisplayId displayId,
         const PointF& position = {100, 200},
         const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                         AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -1659,18 +578,19 @@
 }
 
 static InputEventInjectionResult injectMotionDown(InputDispatcher& dispatcher, int32_t source,
-                                                  int32_t displayId,
+                                                  ui::LogicalDisplayId displayId,
                                                   const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
 }
 
 static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int32_t source,
-                                                int32_t displayId,
+                                                ui::LogicalDisplayId displayId,
                                                 const PointF& location = {100, 200}) {
     return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
 }
 
-static NotifyKeyArgs generateKeyArgs(int32_t action, int32_t displayId = ADISPLAY_ID_NONE) {
+static NotifyKeyArgs generateKeyArgs(
+        int32_t action, ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     // Define a valid key event.
     NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
@@ -1680,30 +600,8 @@
     return args;
 }
 
-static NotifyKeyArgs generateSystemShortcutArgs(int32_t action,
-                                                int32_t displayId = ADISPLAY_ID_NONE) {
-    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    // Define a valid key event.
-    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
-                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_C, KEY_C,
-                       AMETA_META_ON, currentTime);
-
-    return args;
-}
-
-static NotifyKeyArgs generateAssistantKeyArgs(int32_t action,
-                                              int32_t displayId = ADISPLAY_ID_NONE) {
-    nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
-    // Define a valid key event.
-    NotifyKeyArgs args(InputEvent::nextId(), currentTime, /*readTime=*/0, DEVICE_ID,
-                       AINPUT_SOURCE_KEYBOARD, displayId, 0, action, /*flags=*/0, AKEYCODE_ASSIST,
-                       KEY_ASSISTANT, AMETA_NONE, currentTime);
-
-    return args;
-}
-
 [[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
-                                                         int32_t displayId,
+                                                         ui::LogicalDisplayId displayId,
                                                          const std::vector<PointF>& points) {
     size_t pointerCount = points.size();
     if (action == AMOTION_EVENT_ACTION_DOWN || action == AMOTION_EVENT_ACTION_UP) {
@@ -1740,7 +638,8 @@
     return generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, points);
 }
 
-static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId) {
+static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
+                                           ui::LogicalDisplayId displayId) {
     return generateMotionArgs(action, source, displayId, {PointF{100, 200}});
 }
 
@@ -1758,9 +657,9 @@
  */
 TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher,
-                                       "Window that breaks its input channel", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+                                                             "Window that breaks its input channel",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -1771,16 +670,18 @@
 
 TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 using InputDispatcherDeathTest = InputDispatcherTest;
@@ -1794,8 +695,9 @@
     ScopedSilentDeath _silentDeath;
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     ASSERT_DEATH(mDispatcher->onWindowInfosChanged(
                          {{*window->getInfo(), *window->getInfo()}, {}, 0, 0}),
                  "Incorrect WindowInfosUpdate provided");
@@ -1803,17 +705,19 @@
 
 TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // Inject a MotionEvent to an unknown display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::INVALID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -1823,18 +727,19 @@
  */
 TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -1842,37 +747,40 @@
  */
 TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 // The foreground window should receive the first touch down event.
 TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Top window should receive the touch down event. Second window should not receive anything.
-    windowTop->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowTop->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowSecond->assertNoEvents();
 }
 
@@ -1886,10 +794,12 @@
 TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -1903,7 +813,7 @@
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
@@ -1913,13 +823,13 @@
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Now the foreground window goes away, but the wallpaper stays
     mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
     foregroundWindow->consumeMotionCancel();
     // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -1929,8 +839,8 @@
  */
 TEST_F(InputDispatcherTest, CancelAfterPointer0Up) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     // First touch pointer down on right window
@@ -1969,30 +879,32 @@
 TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
             {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     foregroundWindow->consumeMotionMove();
-    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Wallpaper closes its channel, but the window remains.
     wallpaperWindow->destroyReceiver();
@@ -2004,6 +916,301 @@
     foregroundWindow->consumeMotionCancel();
 }
 
+/**
+ * Two windows: left and right, and a separate wallpaper window underneath each. Device A sends a
+ * down event to the left window. Device B sends a down event to the right window. Next, the right
+ * window disappears. Both the right window and its wallpaper window should receive cancel event.
+ * The left window and its wallpaper window should not receive any events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceDisappearingWindowWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Now right foreground window disappears, but right wallpaper window remains.
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    // Left foreground window and left wallpaper window still exist, and should not receive any
+    // events.
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    // Since right foreground window disappeared, right wallpaper window and right foreground window
+    // should receive cancel events.
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap. Every window has a
+ * wallpaper window underneath. The middle window also has SLIPPERY flag.
+ * Device A sends a down event to the left window. Device B sends a down event to the middle window.
+ * Next, device B sends move event to the right window. Touch for device B should slip from the
+ * middle window to the right window. Also, the right wallpaper window should receive a down event.
+ * The middle window and its wallpaper window should receive a cancel event. The left window should
+ * not receive any events. If device B continues to report events, the right window and its
+ * wallpaper window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyTouchWithWallpaperWindow) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    middleForegroundWindow->setSlippery(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A sends a DOWN event to the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B sends a DOWN event to the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Move the events of device B to the top of the right window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Make sure the window on the right can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                   WithDeviceId(deviceB),
+                                                   WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+}
+
+/**
+ * Similar to the test above, we have three windows, they are arranged horizontally and without any
+ * overlap, and every window has a wallpaper window. The middle window is a simple window, without
+ * any special flags. Device A reports a down event that lands in left window. Device B sends a down
+ * event to the middle window and then touch is transferred from the middle window to the right
+ * window. The right window and its wallpaper window should receive a down event. The middle window
+ * and its wallpaper window should receive a cancel event. The left window should not receive any
+ * events. Subsequent events reported by device B should go to the right window and its wallpaper.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceTouchTransferWithWallpaperWindows) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftForegroundWindow->setFrame(Rect(0, 0, 100, 100));
+    leftForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> leftWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWallpaperWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> middleForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleForegroundWindow->setFrame(Rect(100, 0, 200, 100));
+    middleForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> middleWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Middle wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWallpaperWindow->setFrame(Rect(100, 0, 200, 100));
+    middleWallpaperWindow->setIsWallpaper(true);
+
+    sp<FakeWindowHandle> rightForegroundWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right foreground window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightForegroundWindow->setFrame(Rect(200, 0, 300, 100));
+    rightForegroundWindow->setDupTouchToWallpaper(true);
+    sp<FakeWindowHandle> rightWallpaperWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right wallpaper window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWallpaperWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWallpaperWindow->setIsWallpaper(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftForegroundWindow->getInfo(), *leftWallpaperWindow->getInfo(),
+              *middleForegroundWindow->getInfo(), *middleWallpaperWindow->getInfo(),
+              *rightForegroundWindow->getInfo(), *rightWallpaperWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+    // Device A touch down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    leftWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                  WithDeviceId(deviceA),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+    // Device B touch down on the middle window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(EXPECTED_WALLPAPER_FLAGS)));
+
+    // Transfer touch from the middle window to the right window.
+    ASSERT_TRUE(mDispatcher->transferTouchGesture(middleForegroundWindow->getToken(),
+                                                  rightForegroundWindow->getToken()));
+
+    middleForegroundWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_CANCELED)));
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+
+    // Make sure the right window can receive the remaining events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(251).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftForegroundWindow->assertNoEvents();
+    leftWallpaperWindow->assertNoEvents();
+    middleForegroundWindow->assertNoEvents();
+    middleWallpaperWindow->assertNoEvents();
+    rightForegroundWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE),
+                                                    WithDeviceId(deviceB),
+                                                    WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    rightWallpaperWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB),
+                  WithFlags(EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+}
+
 class ShouldSplitTouchFixture : public InputDispatcherTest,
                                 public ::testing::WithParamInterface<bool> {};
 INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
@@ -2016,12 +1223,14 @@
 TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> foregroundWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                       ui::LogicalDisplayId::DEFAULT);
     foregroundWindow->setDupTouchToWallpaper(true);
     foregroundWindow->setPreventSplitting(GetParam());
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -2029,13 +1238,13 @@
 
     // Touch down on top window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both top window and its wallpaper should receive the touch down
     foregroundWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the top window
     const MotionEvent secondFingerDownEvent =
@@ -2048,14 +1257,13 @@
               injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-
     foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
-    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT,
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     const MotionEvent secondFingerUpEvent =
             MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
@@ -2064,14 +1272,17 @@
               injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    foregroundWindow->consumeMotionPointerUp(0);
-    wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    foregroundWindow->consumeMotionPointerUp(/*pointerIdx=*/0,
+                                             WithDisplayId(ui::LogicalDisplayId::DEFAULT));
+    wallpaperWindow->consumeMotionPointerUp(/*pointerIdx=*/0,
+                                            AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                                  WithFlags(EXPECTED_WALLPAPER_FLAGS)));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
                                                    AINPUT_SOURCE_TOUCHSCREEN)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                                         .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
                                                          .x(100)
@@ -2079,8 +1290,8 @@
                                         .build(),
                                 INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    foregroundWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    foregroundWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -2093,18 +1304,19 @@
  */
 TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
     leftWindow->setDupTouchToWallpaper(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
     rightWindow->setDupTouchToWallpaper(true);
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
     wallpaperWindow->setIsWallpaper(true);
 
@@ -2116,13 +1328,13 @@
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Second finger down on the right window
     const MotionEvent secondFingerDownEvent =
@@ -2138,16 +1350,16 @@
 
     leftWindow->consumeMotionMove();
     // Since the touch is split, right window gets ACTION_DOWN
-    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
-                                              expectedWallpaperFlags);
+    rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ui::LogicalDisplayId::DEFAULT,
+                                              EXPECTED_WALLPAPER_FLAGS);
 
     // Now, leftWindow, which received the first finger, disappears.
     mDispatcher->onWindowInfosChanged(
             {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
     leftWindow->consumeMotionCancel();
     // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // The pointer that's still down on the right window moves, and goes to the right window only.
     // As far as the dispatcher's concerned though, both pointers are still present.
@@ -2174,18 +1386,19 @@
  */
 TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
     leftWindow->setDupTouchToWallpaper(true);
     leftWindow->setSlippery(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     sp<FakeWindowHandle> wallpaperWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaperWindow->setIsWallpaper(true);
 
     mDispatcher->onWindowInfosChanged(
@@ -2196,23 +1409,23 @@
 
     // Touch down on left window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both foreground window and its wallpaper should receive the touch down
     leftWindow->consumeMotionDown();
-    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaperWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 
     // Move to right window, the left window should receive cancel.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {201, 100}))
+                                ui::LogicalDisplayId::DEFAULT, {201, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     leftWindow->consumeMotionCancel();
-    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    rightWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    wallpaperWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
 }
 
 /**
@@ -2233,14 +1446,14 @@
  */
 TEST_F(InputDispatcherTest, TwoPointerCancelInconsistentPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -2311,8 +1524,8 @@
 TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 300, 300));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2358,6 +1571,60 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 }
 
+// Still send inject motion events to window which already be touched.
+TEST_F(InputDispatcherTest, AlwaysDispatchInjectMotionEventWhenAlreadyDownForWindow) {
+    std::shared_ptr<FakeApplicationHandle> application1 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "window1",
+                                       ui::LogicalDisplayId::DEFAULT);
+    window1->setFrame(Rect(0, 0, 100, 100));
+    window1->setWatchOutsideTouch(false);
+
+    std::shared_ptr<FakeApplicationHandle> application2 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window2 =
+            sp<FakeWindowHandle>::make(application2, mDispatcher, "window2",
+                                       ui::LogicalDisplayId::DEFAULT);
+    window2->setFrame(Rect(50, 50, 100, 100));
+    window2->setWatchOutsideTouch(true);
+    mDispatcher->onWindowInfosChanged({{*window2->getInfo(), *window1->getInfo()}, {}, 0, 0});
+
+    std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT;
+    InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT;
+    std::optional<gui::Uid> targetUid = {};
+    uint32_t policyFlags = DEFAULT_POLICY_FLAGS;
+
+    const MotionEvent eventDown1 = MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+        .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)).deviceId(-1)
+        .build();
+    injectMotionEvent(*mDispatcher, eventDown1, injectionTimeout, injectionMode, targetUid,
+        policyFlags);
+    window2->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    const MotionEvent eventUp1 = MotionEventBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+        .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)).deviceId(-1)
+        .downTime(eventDown1.getDownTime()).build();
+    // Inject UP event, without the POLICY_FLAG_PASS_TO_USER (to simulate policy behaviour
+    // when screen is off).
+    injectMotionEvent(*mDispatcher, eventUp1, injectionTimeout, injectionMode, targetUid,
+        /*policyFlags=*/0);
+    window2->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    const MotionEvent eventDown2 = MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+        .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40)).deviceId(-1)
+        .build();
+    injectMotionEvent(*mDispatcher, eventDown2, injectionTimeout, injectionMode, targetUid,
+        policyFlags);
+    window1->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window2->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+
+    const MotionEvent eventUp2 = MotionEventBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+        .pointer(PointerBuilder(0, ToolType::FINGER).x(60).y(60)).deviceId(-1)
+        .downTime(eventDown2.getDownTime()).build();
+    injectMotionEvent(*mDispatcher, eventUp2, injectionTimeout, injectionMode, targetUid,
+        /*policyFlags=*/0);
+    window1->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    window2->assertNoEvents();
+}
+
 /**
  * Two windows: a window on the left and a window on the right.
  * Mouse is hovered from the right window into the left window.
@@ -2369,14 +1636,16 @@
  * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
  * In the buggy implementation, a tap on the right window would cause a crash.
  */
-TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -2465,6 +1734,99 @@
 }
 
 /**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered from the right window into the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ * The second tap is done onto the right window.
+ * The mouse and tap are from two different devices.
+ * We technically don't need to set the downtime / eventtime for these events, but setting these
+ * explicitly helps during debugging.
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, a tap on the right window would cause a crash.
+ */
+TEST_F(InputDispatcherTest, HoverFromLeftToRightAndTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+    // All times need to start at the current time, otherwise the dispatcher will drop the events as
+    // stale.
+    const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    const int32_t mouseDeviceId = 6;
+    const int32_t touchDeviceId = 4;
+    // Move the cursor from right
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .downTime(baseTime + 10)
+                    .eventTime(baseTime + 20)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // .. to the left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .downTime(baseTime + 10)
+                    .eventTime(baseTime + 30)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+    // Now tap the left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .downTime(baseTime + 40)
+                    .eventTime(baseTime + 40)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .downTime(baseTime + 40)
+                                      .eventTime(baseTime + 50)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // Tap the window on the right
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .downTime(baseTime + 60)
+                    .eventTime(baseTime + 60)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // release tap
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .downTime(baseTime + 60)
+                                      .eventTime(baseTime + 70)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_UP));
+
+    // No more events
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Start hovering in a window. While this hover is still active, make another window appear on top.
  * The top, obstructing window has no input channel, so it's not supposed to receive input.
  * While the top window is present, the hovering is stopped.
@@ -2475,8 +1837,8 @@
  */
 TEST_F(InputDispatcherTest, HoverWhileWindowAppears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
@@ -2491,7 +1853,7 @@
     // Now, an obscuring window appears!
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
-                                       ADISPLAY_ID_DEFAULT,
+                                       ui::LogicalDisplayId::DEFAULT,
                                        /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
@@ -2524,8 +1886,8 @@
  */
 TEST_F(InputDispatcherTest, HoverMoveWhileWindowAppears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     // Only a single window is present at first
@@ -2540,7 +1902,7 @@
     // Now, an obscuring window appears!
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
-                                       ADISPLAY_ID_DEFAULT,
+                                       ui::LogicalDisplayId::DEFAULT,
                                        /*createInputChannel=*/false);
     obscuringWindow->setFrame(Rect(0, 0, 200, 200));
     obscuringWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
@@ -2582,8 +1944,8 @@
  */
 TEST_F(InputDispatcherTest, HoverMoveAndScroll) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -2605,6 +1967,99 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
 }
 
+/**
+ * Two windows: a trusted overlay and a regular window underneath. Both windows are visible.
+ * Mouse is hovered, and the hover event should only go to the overlay.
+ * However, next, the touchable region of the trusted overlay shrinks. The mouse position hasn't
+ * changed, but the cursor would now end up hovering above the regular window underneatch.
+ * If the mouse is now clicked, this would generate an ACTION_DOWN event, which would go to the
+ * regular window. However, the trusted overlay is also watching for outside touch.
+ * The trusted overlay should get two events:
+ * 1) The ACTION_OUTSIDE event, since the click is now not inside its touchable region
+ * 2) The HOVER_EXIT event, since the mouse pointer is no longer hovering inside this window
+ *
+ * This test reproduces a crash where there is an overlap between dispatch modes for the trusted
+ * overlay touch target, since the event is causing both an ACTION_OUTSIDE, and as a HOVER_EXIT.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlay) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
+ * Similar to above, but also has a spy on top that also catches the HOVER
+ * events. Also, instead of ACTION_DOWN, we are continuing to send the hovering
+ * stream to ensure that the spy receives hover events correctly.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlayWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(app, mDispatcher, "Spy", ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
 using InputDispatcherMultiDeviceTest = InputDispatcherTest;
 
 /**
@@ -2612,9 +2067,10 @@
  * touch is dropped, because stylus should be preferred over touch.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2654,16 +2110,65 @@
 }
 
 /**
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because multiple devices are allowed to be active in the same window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
+    window->assertNoEvents();
+}
+
+/**
  * One window and one spy window. Stylus down on the window. Next, touch from another device goes
  * down. Ensure that touch is dropped, because stylus should be preferred over touch.
  * Similar test as above, but with added SPY window.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
@@ -2712,13 +2217,77 @@
 }
 
 /**
+ * One window and one spy window. Stylus down on the window. Next, touch from another device goes
+ * down. Ensure that touch is not dropped, because multiple devices can be active at the same time.
+ * Similar test as above, but with added SPY window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Touch move
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                        WithCoords(101, 111)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
+}
+
+/**
  * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
  * touch is dropped, because stylus hover takes precedence.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2763,13 +2332,68 @@
 }
 
 /**
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is not dropped, because stylus hover and touch can be both active at the same time.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverDoesNotBlockTouchDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    // Touch move on window
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+    // and subsequent touches continue to work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    window->assertNoEvents();
+}
+
+/**
  * One window. Touch down on the window. Then, stylus hover on the window from another device.
  * Ensure that touch is canceled, because stylus hover should take precedence.
  */
 TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2816,14 +2440,69 @@
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is not canceled, because stylus hover can be active at the same time as touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus hover on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    // Stylus hover movement is received normally
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+                                     WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+                                     WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+    // Subsequent touch movements also work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId),
+                                     WithCoords(142, 147)));
+
+    window->assertNoEvents();
+}
+
+/**
  * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
  * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
  * become active.
  */
 TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2868,13 +2547,62 @@
 }
 
 /**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * both stylus devices can function simultaneously.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TwoStylusDevicesActiveAtTheSameTime) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t stylusDeviceId1 = 3;
+    constexpr int32_t stylusDeviceId2 = 5;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+    // Second stylus down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId2)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId1)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+    window->assertNoEvents();
+}
+
+/**
  * One window. Touch down on the window. Then, stylus down on the window from another device.
  * Ensure that is canceled, because stylus down should be preferred over touch.
  */
 TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -2912,20 +2640,72 @@
 }
 
 /**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that both touch and stylus are functioning independently.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsNotCanceledByStylusDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    constexpr int32_t touchDeviceId = 4;
+    constexpr int32_t stylusDeviceId = 2;
+
+    // Touch down on window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // Stylus down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Subsequent stylus movements are delivered correctly
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+                                     WithCoords(101, 111)));
+
+    // Touch continues to work too
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(148).y(149))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * Two windows: a window on the left and a window on the right.
  * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
  * down. Then, on the left window, also place second touch pointer down.
  * This test tries to reproduce a crash.
  * In the buggy implementation, second pointer down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -2997,16 +2777,98 @@
 
 /**
  * Two windows: a window on the left and a window on the right.
+ * Mouse is clicked on the left window and remains down. Touch is touched on the right and remains
+ * down. Then, on the left window, also place second touch pointer down.
+ * This test tries to reproduce a crash.
+ * In the buggy implementation, second pointer down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // Start hovering over the left window
+    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(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(
+            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(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Second touch pointer down on left window
+    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());
+    // Since this is now a new splittable pointer going down on the left window, and it's coming
+    // from a different device, it will be split and delivered to left window separately.
+    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
+    // current implementation.
+    const std::map<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}};
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers)));
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and a window on the right.
  * Mouse is hovered on the left window and stylus is hovered on the right window.
  */
 TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -3056,21 +2918,22 @@
  * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
  * Check the stream that's received by the spy.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 400, 400));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
 
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
@@ -3126,6 +2989,83 @@
 }
 
 /**
+ * Three windows: a window on the left and a window on the right.
+ * And a spy window that's positioned above all of them.
+ * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
+ * Check the stream that's received by the spy.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus down on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Stylus movements continue. They should be delivered to the left window and to the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Further touch MOVE events keep going to the right window and to the spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Three windows: a window on the left, a window on the right, and a spy window positioned above
  * both.
  * Check hover in left window and touch down in the right window.
@@ -3134,20 +3074,21 @@
  * respectively.
  */
 TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 400, 400));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -3201,6 +3142,84 @@
 }
 
 /**
+ * Three windows: a window on the left, a window on the right, and a spy window positioned above
+ * both.
+ * Check hover in left window and touch down in the right window.
+ * At first, spy should receive hover. Next, spy should receive touch.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverDoesNotBlockTouchWithSpy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 400, 400));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 1;
+    const int32_t touchDeviceId = 2;
+
+    // Stylus hover on the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+    // Touch down on the right window.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                                      .build());
+    leftWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Stylus movements continue. They should be delivered to the left window and the spy.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Touch movements continue. They should be delivered to the right window and the spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spyWindow->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * On a single window, use two different devices: mouse and touch.
  * Touch happens first, with two pointers going down, and then the first pointer leaving.
  * Mouse is clicked next, which causes the touch stream to be aborted with ACTION_CANCEL.
@@ -3208,10 +3227,11 @@
  * because the mouse is currently down, and a POINTER_DOWN event from the touchscreen does not
  * represent a new gesture.
  */
-TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3281,13 +3301,92 @@
 }
 
 /**
+ * On a single window, use two different devices: mouse and touch.
+ * Touch happens first, with two pointers going down, and then the first pointer leaving.
+ * Mouse is clicked next, which should not interfere with the touch stream.
+ * Finally, a second touch pointer goes down again. Ensure the second touch pointer is also
+ * delivered correctly.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // First touch pointer down
+    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(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(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
+    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_DOWN), WithDeviceId(mouseDeviceId)));
+
+    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(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());
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_0_DOWN), WithDeviceId(touchDeviceId),
+                                     WithPointerCount(2u)));
+
+    // Mouse movements should continue to work
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(mouseDeviceId)
+                                      .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+    window->assertNoEvents();
+}
+
+/**
  * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
  * the injected event.
  */
-TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3317,6 +3416,40 @@
 }
 
 /**
+ * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event runs
+ * parallel to the injected event.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 400, 400));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    // Pretend a test injects an ACTION_DOWN mouse event, but forgets to lift up the touch after
+    // completion.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(*mDispatcher,
+                                MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .deviceId(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID)
+                                        .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                                        .build()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(VIRTUAL_KEYBOARD_ID)));
+
+    // Now a real touch comes. The injected pointer will remain, and the new gesture will also be
+    // allowed through.
+    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_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * This test is similar to the test above, but the sequence of injected events is different.
  *
  * Two windows: a window on the left and a window on the right.
@@ -3330,14 +3463,15 @@
  * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
  * In the buggy implementation, second finger down on the left window would cause a crash.
  */
-TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 200, 200));
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(200, 0, 400, 200));
 
     mDispatcher->onWindowInfosChanged(
@@ -3402,14 +3536,91 @@
 }
 
 /**
+ * This test is similar to the test above, but the sequence of injected events is different.
+ *
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered over the left window.
+ * Next, we tap on the left window, where the cursor was last seen.
+ *
+ * After that, we send one finger down onto the right window, and then a second finger down onto
+ * the left window.
+ * The touch is split, so this last gesture should cause 2 ACTION_DOWN events, one in the right
+ * window (first), and then another on the left window (second).
+ * This test reproduces a crash where there is a mismatch between the downTime and eventTime.
+ * In the buggy implementation, second finger down on the left window would cause a crash.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 6;
+    const int32_t touchDeviceId = 4;
+    // Hover over the left window. Keep the cursor there.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .deviceId(mouseDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                    .build());
+    leftWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+    // Tap on left window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                    .build());
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithDeviceId(touchDeviceId)));
+
+    // First finger down on right window
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+                    .build());
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+
+    // Second finger down on the left window
+    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(WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+
+    // No more events
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
  * While the touch is down, new hover events from the stylus device should be ignored. After the
  * touch is gone, stylus hovering should start working again.
  */
 TEST_F(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3468,6 +3679,61 @@
 }
 
 /**
+ * Start hovering with a stylus device, and then tap with a touch device. Ensure no crash occurs.
+ * While the touch is down, hovering from the stylus is not affected. After the touch is gone,
+ * check that the stylus hovering continues to work.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverWithTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t stylusDeviceId = 5;
+    const int32_t touchDeviceId = 4;
+    // Start hovering with stylus
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Finger down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Continue hovering with stylus.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
+                                      .build());
+    // Hovers continue to work
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+    // Lift up the finger
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(touchDeviceId)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+    window->assertNoEvents();
+}
+
+/**
  * If stylus is down anywhere on the screen, then touches should not be delivered to windows that
  * have InputConfig::GLOBAL_STYLUS_BLOCKS_TOUCH.
  *
@@ -3480,12 +3746,13 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
 
     sp<FakeWindowHandle> sbtRightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher,
-                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+                                       "Stylus blocks touch (right) window",
+                                       ui::LogicalDisplayId::DEFAULT);
     sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
     sbtRightWindow->setGlobalStylusBlocksTouch(true);
 
@@ -3552,12 +3819,13 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
 
     sp<FakeWindowHandle> sbtRightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher,
-                                       "Stylus blocks touch (right) window", ADISPLAY_ID_DEFAULT);
+                                       "Stylus blocks touch (right) window",
+                                       ui::LogicalDisplayId::DEFAULT);
     sbtRightWindow->setFrame(Rect(100, 100, 200, 200));
     sbtRightWindow->setGlobalStylusBlocksTouch(true);
 
@@ -3618,13 +3886,13 @@
  */
 TEST_F(InputDispatcherTest, StylusHoverAndDownNoInputChannel) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setNoInputChannel(true);
     window->setFrame(Rect(0, 0, 200, 200));
 
@@ -3677,8 +3945,8 @@
 TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -3713,15 +3981,16 @@
  * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
  * While the mouse is down, new move events from the touch device should be ignored.
  */
-TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 200, 200));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -3810,6 +4079,114 @@
 }
 
 /**
+ * Start hovering with a mouse, and then tap with a touch device. Pilfer the touch stream.
+ * Next, click with the mouse device. Both windows (spy and regular) should receive the new mouse
+ * ACTION_DOWN event because that's a new gesture, and pilfering should no longer be active.
+ * While the mouse is down, new move events from the touch device should continue to work.
+ */
+TEST_F(InputDispatcherTest, TouchPilferAndMouseMove) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+
+    // Hover a bit with mouse first
+    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(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    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));
+
+    // Pilfer the stream
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+    // Hover is not pilfered! Only touch.
+
+    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(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_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(mouseDeviceId)));
+
+    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(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(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+    // Touch move!
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(65).y(65))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    // No more events
+    spyWindow->assertNoEvents();
+    window->assertNoEvents();
+}
+
+/**
  * On the display, have a single window, and also an area where there's no window.
  * First pointer touches the "no window" area of the screen. Second pointer touches the window.
  * Make sure that the window receives the second pointer, and first pointer is simply ignored.
@@ -3932,16 +4309,133 @@
     window2->consumeMotionEvent(WithDownTime(secondDown->getDownTime()));
 }
 
+/**
+ * When events are not split, the downTime should be adjusted such that the downTime corresponds
+ * to the event time of the first ACTION_DOWN. If a new window appears, it should not affect
+ * the event routing because the first window prevents splitting.
+ */
+TEST_F(InputDispatcherTest, SplitTouchesSendCorrectActionDownTimeForNewWindow) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+    window1->setPreventSplitting(true);
+
+    sp<FakeWindowHandle> window2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo()}, {}, 0, 0});
+
+    // Touch down on the first window
+    NotifyMotionArgs downArgs = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                        .build();
+    mDispatcher->notifyMotion(downArgs);
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDownTime(downArgs.downTime)));
+
+    // Second window is added
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    window1->consumeMotionPointerDown(1, AllOf(WithDownTime(downArgs.downTime)));
+
+    // Finish the gesture
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    window1->consumeMotionPointerUp(1, AllOf(WithDownTime(downArgs.downTime)));
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDownTime(downArgs.downTime)));
+    window2->assertNoEvents();
+}
+
+/**
+ * When splitting touch events, the downTime should be adjusted such that the downTime corresponds
+ * to the event time of the first ACTION_DOWN sent to the new window.
+ * If a new window that does not support split appears on the screen and gets touched with the
+ * second finger, it should not get any events because it doesn't want split touches. At the same
+ * time, the first window should not get the pointer_down event because it supports split touches
+ * (and the touch occurred outside of the bounds of window1).
+ */
+TEST_F(InputDispatcherTest, SplitTouchesDropsEventForNonSplittableSecondWindow) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window1 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
+    window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+
+    sp<FakeWindowHandle> window2 =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
+    window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo()}, {}, 0, 0});
+
+    // Touch down on the first window
+    NotifyMotionArgs downArgs = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                        .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                        .build();
+    mDispatcher->notifyMotion(downArgs);
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDownTime(downArgs.downTime)));
+
+    // Second window is added
+    window2->setPreventSplitting(true);
+    mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
+
+    // Now touch down on the window with another pointer
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    // Event is dropped because window2 doesn't support split touch, and window1 does.
+
+    // Complete the gesture
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+    // A redundant MOVE event is generated that doesn't carry any new information
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_MOVE), WithDownTime(downArgs.downTime)));
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .downTime(downArgs.downTime)
+                                      .build());
+
+    window1->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithDownTime(downArgs.downTime)));
+    window1->assertNoEvents();
+    window2->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowLeft =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowLeft = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    sp<FakeWindowHandle> windowRight =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowRight = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
@@ -4001,7 +4495,7 @@
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
-    windowLeft->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    windowLeft->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     // Move mouse cursor back to right window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -4022,10 +4516,11 @@
  * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
  * currently active gesture should be canceled, and the new one should proceed.
  */
-TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4076,19 +4571,274 @@
     window->assertNoEvents();
 }
 
+/**
+ * Put two fingers down (and don't release them) and click the mouse button.
+ * The clicking of mouse is a new ACTION_DOWN event. Since it's from a different device, the
+ * currently active gesture should not be canceled, and the new one should proceed in parallel.
+ */
+TEST_F(InputDispatcherTest, TwoPointersDownMouseClick) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t touchDeviceId = 4;
+    const int32_t mouseDeviceId = 6;
+
+    // Two pointers down
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+                                      .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));
+
+    // Send a series of mouse events for a mouse click
+    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_DOWN), WithDeviceId(mouseDeviceId)));
+
+    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 active gesture, it should be sent normally.
+    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->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+    window->assertNoEvents();
+}
+
+/**
+ * A spy window sits above a window with NO_INPUT_CHANNEL. Ensure that the spy receives events even
+ * though the window underneath should not get any events.
+ */
+TEST_F(InputDispatcherTest, NonSplittableSpyAboveNoInputChannelWindowSinglePointer) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 100, 100));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setPreventSplitting(true);
+    spyWindow->setSpy(true);
+    // Another window below spy that has both NO_INPUT_CHANNEL and PREVENT_SPLITTING
+    sp<FakeWindowHandle> inputSinkWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Input sink below spy",
+                                       ui::LogicalDisplayId::DEFAULT);
+    inputSinkWindow->setFrame(Rect(0, 0, 100, 100));
+    inputSinkWindow->setTrustedOverlay(true);
+    inputSinkWindow->setPreventSplitting(true);
+    inputSinkWindow->setNoInputChannel(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *inputSinkWindow->getInfo()}, {}, 0, 0});
+
+    // Tap the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(51))
+                                      .build());
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(51))
+                    .build());
+
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP)));
+    inputSinkWindow->assertNoEvents();
+}
+
+/**
+ * A spy window sits above a window with NO_INPUT_CHANNEL. Ensure that the spy receives events even
+ * though the window underneath should not get any events.
+ * Same test as above, but with two pointers touching instead of one.
+ */
+TEST_F(InputDispatcherTest, NonSplittableSpyAboveNoInputChannelWindowTwoPointers) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 100, 100));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setPreventSplitting(true);
+    spyWindow->setSpy(true);
+    // Another window below spy that would have both NO_INPUT_CHANNEL and PREVENT_SPLITTING
+    sp<FakeWindowHandle> inputSinkWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Input sink below spy",
+                                       ui::LogicalDisplayId::DEFAULT);
+    inputSinkWindow->setFrame(Rect(0, 0, 100, 100));
+    inputSinkWindow->setTrustedOverlay(true);
+    inputSinkWindow->setPreventSplitting(true);
+    inputSinkWindow->setNoInputChannel(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *inputSinkWindow->getInfo()}, {}, 0, 0});
+
+    // Both fingers land into the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(51))
+                                      .build());
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(51))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(11))
+                    .build());
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(51))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(11))
+                    .build());
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(51))
+                    .build());
+
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+    spyWindow->consumeMotionPointerDown(1, WithPointerCount(2));
+    spyWindow->consumeMotionPointerUp(1, WithPointerCount(2));
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP)));
+    inputSinkWindow->assertNoEvents();
+}
+
+/** Check the behaviour for cases where input sink prevents or doesn't prevent splitting. */
+class SpyThatPreventsSplittingWithApplicationFixture : public InputDispatcherTest,
+                                                       public ::testing::WithParamInterface<bool> {
+};
+
+/**
+ * Three windows:
+ * - An application window (app window)
+ * - A spy window that does not overlap the app window. Has PREVENT_SPLITTING flag
+ * - A window below the spy that has NO_INPUT_CHANNEL (call it 'inputSink')
+ *
+ * The spy window is side-by-side with the app window. The inputSink is below the spy.
+ * We first touch the area outside of the appWindow, but inside spyWindow.
+ * Only the SPY window should get the DOWN event.
+ * The spy pilfers after receiving the first DOWN event.
+ * Next, we touch the app window.
+ * The spy should receive POINTER_DOWN(1) (since spy is preventing splits).
+ * Also, since the spy is already pilfering the first pointer, it will be sent the remaining new
+ * pointers automatically, as well.
+ * Next, the first pointer (from the spy) is lifted.
+ * Spy should get POINTER_UP(0).
+ * This event should not go to the app because the app never received this pointer to begin with.
+ * Now, lift the remaining pointer and check that the spy receives UP event.
+ *
+ * Finally, send a new ACTION_DOWN event to the spy and check that it's received.
+ * This test attempts to reproduce a crash in the dispatcher.
+ */
+TEST_P(SpyThatPreventsSplittingWithApplicationFixture, SpyThatPreventsSplittingWithApplication) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(100, 100, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setPreventSplitting(true);
+    spyWindow->setSpy(true);
+    // Another window below spy that has both NO_INPUT_CHANNEL and PREVENT_SPLITTING
+    sp<FakeWindowHandle> inputSinkWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Input sink below spy",
+                                       ui::LogicalDisplayId::DEFAULT);
+    inputSinkWindow->setFrame(Rect(100, 100, 200, 200)); // directly below the spy
+    inputSinkWindow->setTrustedOverlay(true);
+    inputSinkWindow->setPreventSplitting(GetParam());
+    inputSinkWindow->setNoInputChannel(true);
+
+    sp<FakeWindowHandle> appWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "App",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    appWindow->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *inputSinkWindow->getInfo(), *appWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    // First finger lands outside of the appWindow, but inside of the spy window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+                                      .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+    mDispatcher->pilferPointers(spyWindow->getToken());
+
+    // Second finger lands in the app, and goes to the spy window. It doesn't go to the app because
+    // the spy is already pilfering the first pointer, and this automatically grants the remaining
+    // new pointers to the spy, as well.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
+                    .build());
+
+    spyWindow->consumeMotionPointerDown(1, WithPointerCount(2));
+
+    // Now lift up the first pointer
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
+                    .build());
+    spyWindow->consumeMotionPointerUp(0, WithPointerCount(2));
+
+    // And lift the remaining pointer!
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
+                    .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithPointerCount(1)));
+
+    // Now send a new DOWN, which should again go to spy.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+                                      .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+    // The app window doesn't get any events this entire time because the spy received the events
+    // first and pilfered, which makes all new pointers go to it as well.
+    appWindow->assertNoEvents();
+}
+
+// Behaviour should be the same regardless of whether inputSink supports splitting.
+INSTANTIATE_TEST_SUITE_P(SpyThatPreventsSplittingWithApplication,
+                         SpyThatPreventsSplittingWithApplicationFixture, testing::Bool());
+
 TEST_F(InputDispatcherTest, HoverWithSpyWindows) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 600, 800));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
@@ -4108,19 +4858,20 @@
     spyWindow->assertNoEvents();
 }
 
-TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setFrame(Rect(0, 0, 600, 800));
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 600, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     // Send mouse cursor to the window
@@ -4214,15 +4965,111 @@
     spyWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherTest, MouseAndTouchWithSpyWindows) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 600, 800));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 600, 800));
+
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
+    mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Send mouse cursor to the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(100))
+                                      .build());
+
+    // Move mouse cursor
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithSource(AINPUT_SOURCE_MOUSE)));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    // Touch down on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // pilfer the motion, retaining the gesture on the spy window.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindow->getToken()));
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    // Mouse hover is not pilfered
+
+    // Touch UP on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .build());
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Previously, a touch was pilfered. However, that gesture was just finished. Now, we are going
+    // to send a new gesture. It should again go to both windows (spy and the window below), just
+    // like the first gesture did, before pilfering. The window configuration has not changed.
+
+    // One more tap - DOWN
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Touch UP on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(SECOND_DEVICE_ID)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(250))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_UP), WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    // Mouse movement continues normally as well
+    // Move mouse cursor
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(120).y(130))
+                                      .build());
+    window->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+    spyWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+
+    window->assertNoEvents();
+    spyWindow->assertNoEvents();
+}
+
 // This test is different from the test above that HOVER_ENTER and HOVER_EXIT events are injected
 // directly in this test.
 TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4269,7 +5116,7 @@
                                         .buttonState(0)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(400))
                                         .build()));
-    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     // We already canceled the hovering implicitly by injecting the "DOWN" event without lifting the
     // hover first. Therefore, injection of HOVER_EXIT is inconsistent, and should fail.
@@ -4288,11 +5135,11 @@
  */
 TEST_F(InputDispatcherTest, HoverExitIsSentToRemovedWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4316,11 +5163,11 @@
                   REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
                                                        a11y_crash_on_inconsistent_event_stream))) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4337,12 +5184,61 @@
 }
 
 /**
+ * Invalid events injected by input filter are rejected.
+ */
+TEST_F(InputDispatcherTest, InvalidA11yEventsGetRejected) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // a11y sets 'POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY' policy flag during injection, so define
+    // a custom injection function here for convenience.
+    auto injectFromAccessibility = [&](int32_t action, float x, float y) {
+        MotionEvent event = MotionEventBuilder(action, AINPUT_SOURCE_TOUCHSCREEN)
+                                    .pointer(PointerBuilder(0, ToolType::FINGER).x(x).y(y))
+                                    .addFlag(AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT)
+                                    .build();
+        return injectMotionEvent(*mDispatcher, event, 100ms,
+                                 InputEventInjectionSync::WAIT_FOR_RESULT, /*targetUid=*/{},
+                                 POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_FILTERED |
+                                         POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY);
+    };
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectFromAccessibility(ACTION_DOWN, /*x=*/300, /*y=*/400));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectFromAccessibility(ACTION_MOVE, /*x=*/310, /*y=*/420));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    // finger is still down, so a new DOWN event should be rejected!
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectFromAccessibility(ACTION_DOWN, /*x=*/340, /*y=*/410));
+
+    // if the gesture is correctly finished, new down event will succeed
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectFromAccessibility(ACTION_MOVE, /*x=*/320, /*y=*/430));
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectFromAccessibility(ACTION_UP, /*x=*/320, /*y=*/430));
+    window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectFromAccessibility(ACTION_DOWN, /*x=*/350, /*y=*/460));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
  * If mouse is hovering when the touch goes down, the hovering should be stopped via HOVER_EXIT.
  */
-TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4370,14 +5266,46 @@
 }
 
 /**
+ * If mouse is hovering when the touch goes down, the hovering should not be stopped.
+ */
+TEST_F(InputDispatcherTest, TouchDownAfterMouseHover) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const int32_t mouseDeviceId = 7;
+    const int32_t touchDeviceId = 4;
+
+    // Start hovering with the mouse
+    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(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(touchDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+}
+
+/**
  * Inject a mouse hover event followed by a tap from touchscreen.
  * The tap causes a HOVER_EXIT event to be generated because the current event
  * stream's source has been switched.
  */
-TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4408,11 +5336,50 @@
                                              WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
 }
 
+/**
+ * Send a mouse hover event followed by a tap from touchscreen.
+ * The tap causes a HOVER_EXIT event to be generated because the current event
+ * stream's source has been switched.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+
+    // Tap on the window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT),
+                                     WithSource(AINPUT_SOURCE_MOUSE)));
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                     WithSource(AINPUT_SOURCE_TOUCHSCREEN)));
+}
+
 TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowDefaultDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     windowDefaultDisplay->setFrame(Rect(0, 0, 600, 800));
     sp<FakeWindowHandle> windowSecondDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "SecondDisplay",
@@ -4428,7 +5395,7 @@
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(300).y(600))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
@@ -4447,7 +5414,7 @@
               injectMotionEvent(*mDispatcher,
                                 MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
                                                    AINPUT_SOURCE_MOUSE)
-                                        .displayId(ADISPLAY_ID_DEFAULT)
+                                        .displayId(ui::LogicalDisplayId::DEFAULT)
                                         .pointer(PointerBuilder(0, ToolType::MOUSE).x(400).y(700))
                                         .build()));
     windowDefaultDisplay->consumeMotionEvent(
@@ -4459,14 +5426,14 @@
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> windowLeft =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowLeft = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     windowLeft->setFrame(Rect(0, 0, 600, 800));
-    sp<FakeWindowHandle> windowRight =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowRight = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     windowRight->setFrame(Rect(600, 0, 1200, 800));
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged(
             {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
@@ -4475,15 +5442,16 @@
     // left window. This event should be dispatched to the left window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
-                                ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
-    windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                ui::LogicalDisplayId::DEFAULT, {610, 400}, {599, 400}));
+    windowLeft->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowRight->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4491,41 +5459,44 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // When device reset happens, that key stream should be terminated with FLAG_CANCELED
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT, AKEY_EVENT_FLAG_CANCELED);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT, AKEY_EVENT_FLAG_CANCELED);
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsMotionStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive motion down event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // When device reset happens, that motion stream should be terminated with ACTION_CANCEL
     // on the app side.
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
     window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 }
 
 TEST_F(InputDispatcherTest, NotifyDeviceResetCancelsHoveringStream) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4548,8 +5519,9 @@
 
 TEST_F(InputDispatcherTest, InterceptKeyByPolicy) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4557,13 +5529,14 @@
 
     window->consumeFocusEvent(true);
 
-    const NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyArgs =
+            generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
     const std::chrono::milliseconds interceptKeyTimeout = 50ms;
     const nsecs_t injectTime = keyArgs.eventTime;
     mFakePolicy->setInterceptKeyTimeout(interceptKeyTimeout);
     mDispatcher->notifyKey(keyArgs);
     // The dispatching time should be always greater than or equal to intercept key timeout.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     ASSERT_TRUE((systemTime(SYSTEM_TIME_MONOTONIC) - injectTime) >=
                 std::chrono::nanoseconds(interceptKeyTimeout).count());
 }
@@ -4573,8 +5546,9 @@
  */
 TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4582,15 +5556,15 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Set a value that's significantly larger than the default consumption timeout. If the
     // implementation is correct, the actual value doesn't matter; it won't slow down the test.
     mFakePolicy->setInterceptKeyTimeout(600ms);
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
     // Window should receive key event immediately when same key up.
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -4602,13 +5576,13 @@
  */
 TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinates) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect{0, 0, 100, 100});
 
     sp<FakeWindowHandle> outsideWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     outsideWindow->setFrame(Rect{100, 100, 200, 200});
     outsideWindow->setWatchOutsideTouch(true);
     // outsideWindow must be above 'window' to receive ACTION_OUTSIDE events when 'window' is tapped
@@ -4616,8 +5590,8 @@
 
     // Tap on first window.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{50, 50}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::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.
@@ -4626,12 +5600,102 @@
 
     // Ensure outsideWindow doesn't get any more events for the gesture.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{51, 51}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{51, 51}}));
     window->consumeMotionMove();
     outsideWindow->assertNoEvents();
 }
 
 /**
+ * Three windows:
+ * - Left window
+ * - Right window
+ * - Outside window(watch for ACTION_OUTSIDE events)
+ * The windows "left" and "outside" share the same owner, the window "right" has a different owner,
+ * In order to allow the outside window can receive the ACTION_OUTSIDE events, the outside window is
+ * positioned above the "left" and "right" windows, and it doesn't overlap with them.
+ *
+ * First, device A report a down event landed in the right window, the outside window can receive
+ * an ACTION_OUTSIDE event that with zeroed coordinates, the device B report a down event landed
+ * in the left window, the outside window can receive an ACTION_OUTSIDE event the with valid
+ * coordinates, after these, device A and device B continue report MOVE event, the right and left
+ * window can receive it, but outside window event can't receive it.
+ */
+TEST_F(InputDispatcherTest, ActionOutsideForOwnedWindowHasValidCoordinatesWhenMultiDevice) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect{0, 0, 100, 100});
+    leftWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+    sp<FakeWindowHandle> outsideWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Outside Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    outsideWindow->setFrame(Rect{100, 100, 200, 200});
+    outsideWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    outsideWindow->setWatchOutsideTouch(true);
+
+    std::shared_ptr<FakeApplicationHandle> anotherApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect{100, 0, 200, 100});
+    rightWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+
+    // OutsideWindow must be above left window and right window to receive ACTION_OUTSIDE events
+    // when left window or right window is tapped
+    mDispatcher->onWindowInfosChanged(
+            {{*outsideWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on right window use device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+    // Right window is belonged to another owner, so outsideWindow should receive ACTION_OUTSIDE
+    // with zeroed coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceA), WithCoords(0, 0)));
+
+    // Tap on left window use device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    // Because new gesture down on the left window that has the same owner with outside Window, the
+    // outside Window should receive the ACTION_OUTSIDE with coords.
+    outsideWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_OUTSIDE), WithDeviceId(deviceB), WithCoords(-50, -50)));
+
+    // Ensure that windows that can only accept outside do not receive remaining gestures
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(51).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+    outsideWindow->assertNoEvents();
+}
+
+/**
  * This test documents the behavior of WATCH_OUTSIDE_TOUCH. The window will get ACTION_OUTSIDE when
  * a another pointer causes ACTION_DOWN to be sent to another window for the first time. Only one
  * ACTION_OUTSIDE event is sent per gesture.
@@ -4639,32 +5703,33 @@
 TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) {
     // There are three windows that do not overlap. `window` wants to WATCH_OUTSIDE_TOUCH.
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "First Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setWatchOutsideTouch(true);
     window->setFrame(Rect{0, 0, 100, 100});
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect{100, 100, 200, 200});
     sp<FakeWindowHandle> thirdWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Third Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     thirdWindow->setFrame(Rect{200, 200, 300, 300});
     mDispatcher->onWindowInfosChanged(
             {{*window->getInfo(), *secondWindow->getInfo(), *thirdWindow->getInfo()}, {}, 0, 0});
 
     // First pointer lands outside all windows. `window` does not get ACTION_OUTSIDE.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{-10, -10}}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::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.
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {PointF{-10, -10}, PointF{105, 105}}));
     const std::map<int32_t, PointF> expectedPointers{{0, PointF{-10, -10}}, {1, PointF{105, 105}}};
     window->consumeMotionEvent(
@@ -4675,7 +5740,8 @@
     // 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.
     mDispatcher->notifyMotion(
-            generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT,
                                {PointF{-10, -10}, PointF{105, 105}, PointF{205, 205}}));
     window->assertNoEvents();
     secondWindow->consumeMotionMove();
@@ -4684,8 +5750,9 @@
 
 TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -4693,13 +5760,15 @@
 
     window->consumeFocusEvent(true);
 
-    const NotifyKeyArgs keyDown = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
-    const NotifyKeyArgs keyUp = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    const NotifyKeyArgs keyDown =
+            generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
+    const NotifyKeyArgs keyUp =
+            generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT);
     mDispatcher->notifyKey(keyDown);
     mDispatcher->notifyKey(keyUp);
 
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     // All windows are removed from the display. Ensure that we can no longer dispatch to it.
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -4712,23 +5781,25 @@
 }
 
 TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     // Ensure window is non-split and have some transform.
     window->setPreventSplitting(true);
     window->setWindowOffset(20, 40);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(-30).y(-50))
@@ -4756,15 +5827,16 @@
  * "incomplete" gestures.
  */
 TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left splittable Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     leftWindow->setPreventSplitting(false);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> rightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right non-splittable Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     rightWindow->setPreventSplitting(true);
     rightWindow->setFrame(Rect(100, 100, 200, 200));
     mDispatcher->onWindowInfosChanged(
@@ -4786,6 +5858,273 @@
 }
 
 /**
+ * Three windows:
+ * 1) A window on the left, with flag dup_to_wallpaper
+ * 2) A window on the right, with flag slippery
+ * 3) A wallpaper window  under the left window
+ * When touch slips from right window to left, the wallpaper should receive a similar slippery
+ * enter event. Later on, when another device becomes active, the wallpaper should receive
+ * consistent streams from the new device, and also from the old device.
+ * This test attempts to reproduce a crash in the dispatcher where the wallpaper target's downTime
+ * was not getting set during slippery entrance.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlipperyAndMultiWindowMultiTouch) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application1 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application2 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application3 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> wallpaper =
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
+    wallpaper->setIsWallpaper(true);
+    wallpaper->setPreventSplitting(true);
+    wallpaper->setTouchable(false);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application2, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setTouchableRegion(Region{{0, 0, 100, 100}});
+    leftWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application3, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setTouchableRegion(Region{{100, 0, 200, 100}});
+    rightWindow->setSlippery(true);
+    rightWindow->setWatchOutsideTouch(true);
+    rightWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*rightWindow->getInfo(), *leftWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 3;
+    const DeviceId deviceB = 9;
+
+    // First finger from device A into right window
+    NotifyMotionArgs deviceADownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .deviceId(deviceA)
+                    .build();
+
+    mDispatcher->notifyMotion(deviceADownArgs);
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Move the finger of device A from right window into left window. It should slip.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(80).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    wallpaper->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Finger from device B down into left window
+    NotifyMotionArgs deviceBDownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+                    .deviceId(deviceB)
+                    .build();
+    mDispatcher->notifyMotion(deviceBDownArgs);
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_OUTSIDE)));
+
+    // Move finger from device B, still keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+
+    // Lift the finger from device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+
+    // Move the finger of device A, keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE)));
+
+    // Second finger down from device A, into the right window. It should be split into:
+    // MOVE for the left window (due to existing implementation) + a DOWN into the right window
+    // Wallpaper will not receive this new pointer, and it will only get the MOVE event.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    auto firstFingerMoveFromDeviceA = AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_MOVE),
+                                            WithPointerCount(1), WithPointerId(0, 0));
+    leftWindow->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    wallpaper->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    rightWindow->consumeMotionEvent(
+            AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_DOWN), WithPointerId(0, 1)));
+
+    // Lift up the second finger.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    leftWindow->consumeMotionEvent(firstFingerMoveFromDeviceA);
+    wallpaper->consumeMotionEvent(firstFingerMoveFromDeviceA);
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+    rightWindow->assertNoEvents();
+}
+
+/**
+ * Same test as above, but with enable_multi_device_same_window_stream flag set to false.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlipperyAndMultiWindowMultiTouch_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
+    std::shared_ptr<FakeApplicationHandle> application1 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application2 = std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application3 = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> wallpaper =
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
+    wallpaper->setIsWallpaper(true);
+    wallpaper->setPreventSplitting(true);
+    wallpaper->setTouchable(false);
+
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application2, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setTouchableRegion(Region{{0, 0, 100, 100}});
+    leftWindow->setDupTouchToWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application3, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setTouchableRegion(Region{{100, 0, 200, 100}});
+    rightWindow->setSlippery(true);
+    rightWindow->setWatchOutsideTouch(true);
+    rightWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*rightWindow->getInfo(), *leftWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 3;
+    const DeviceId deviceB = 9;
+
+    // First finger from device A into right window
+    NotifyMotionArgs deviceADownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .deviceId(deviceA)
+                    .build();
+
+    mDispatcher->notifyMotion(deviceADownArgs);
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Move the finger of device A from right window into left window. It should slip.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(80).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    rightWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    wallpaper->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+    // Finger from device B down into left window
+    NotifyMotionArgs deviceBDownArgs =
+            MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+                    .deviceId(deviceB)
+                    .build();
+    mDispatcher->notifyMotion(deviceBDownArgs);
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_CANCEL)));
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_CANCEL)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_DOWN)));
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_OUTSIDE)));
+
+    // Move finger from device B, still keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_MOVE)));
+
+    // Lift the finger from device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(50))
+                                      .deviceId(deviceB)
+                                      .downTime(deviceBDownArgs.downTime)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+    wallpaper->consumeMotionEvent(AllOf(WithDeviceId(deviceB), WithMotionAction(ACTION_UP)));
+
+    // Move the finger of device A, keeping it in the left window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    // This device was already canceled, so MOVE events will not be arriving to the windows from it.
+
+    // Second finger down from device A, into the right window. It should be split into:
+    // MOVE for the left window (due to existing implementation) + a DOWN into the right window
+    // Wallpaper will not receive this new pointer, and it will only get the MOVE event.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_DOWN), WithPointerId(0, 1)));
+
+    // Lift up the second finger.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(140).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+
+    rightWindow->consumeMotionEvent(AllOf(WithDeviceId(deviceA), WithMotionAction(ACTION_UP)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(70).y(50))
+                                      .deviceId(deviceA)
+                                      .downTime(deviceADownArgs.downTime)
+                                      .build());
+    rightWindow->assertNoEvents();
+}
+
+/**
  * Two windows: left and right. The left window has PREVENT_SPLITTING input config. Device A sends a
  * down event to the right window. Device B sends a down event to the left window, and then a
  * POINTER_DOWN event to the right window. However, since the left window prevents splitting, the
@@ -4793,16 +6132,17 @@
  * This test attempts to reproduce a crash.
  */
 TEST_F(InputDispatcherTest, MultiDeviceTwoWindowsPreventSplitting) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> leftWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Left window (prevent splitting)",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     leftWindow->setPreventSplitting(true);
 
     sp<FakeWindowHandle> rightWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(100, 0, 200, 100));
 
     mDispatcher->onWindowInfosChanged(
@@ -4853,12 +6193,12 @@
 
 TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     sp<FakeWindowHandle> trustedOverlay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Trusted Overlay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     trustedOverlay->setSpy(true);
     trustedOverlay->setTrustedOverlay(true);
 
@@ -4921,8 +6261,8 @@
 
 TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeNotSentToSingleWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -4978,8 +6318,8 @@
  */
 TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 400, 400));
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
@@ -5044,13 +6384,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> spyWindowDefaultDisplay =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                       ui::LogicalDisplayId::DEFAULT);
     spyWindowDefaultDisplay->setTrustedOverlay(true);
     spyWindowDefaultDisplay->setSpy(true);
 
     sp<FakeWindowHandle> windowDefaultDisplay =
             sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     windowDefaultDisplay->setWindowTransform(1, 0, 0, 1);
 
     sp<FakeWindowHandle> windowSecondDisplay = windowDefaultDisplay->clone(SECOND_DISPLAY_ID);
@@ -5064,10 +6405,10 @@
              0,
              0});
 
-    // Send down to ADISPLAY_ID_DEFAULT
+    // Send down to ui::LogicalDisplayId::DEFAULT
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 100}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     spyWindowDefaultDisplay->consumeMotionDown();
@@ -5080,10 +6421,10 @@
     ASSERT_NE(nullptr, event);
     EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
 
-    // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
-    // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
-    // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
-    // SECOND_DISPLAY_ID, the x and y coordinates are 200
+    // The cancel event is sent to windowDefaultDisplay of the ui::LogicalDisplayId::DEFAULT
+    // display, so the coordinates of the cancel are converted by windowDefaultDisplay's transform,
+    // the x and y coordinates are both 100, otherwise if the cancel event is sent to
+    // windowSecondDisplay of SECOND_DISPLAY_ID, the x and y coordinates are 200
     EXPECT_EQ(100, event->getX(0));
     EXPECT_EQ(100, event->getY(0));
 }
@@ -5102,7 +6443,7 @@
         removeAllWindowsAndDisplays();
     }
 
-    void addDisplayInfo(int displayId, const ui::Transform& transform) {
+    void addDisplayInfo(ui::LogicalDisplayId displayId, const ui::Transform& transform) {
         gui::DisplayInfo info;
         info.displayId = displayId;
         info.transform = transform;
@@ -5127,7 +6468,7 @@
         // respectively.
         ui::Transform displayTransform;
         displayTransform.set(2, 0, 0, 4);
-        addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+        addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
 
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
@@ -5135,13 +6476,13 @@
         // Add two windows to the display. Their frames are represented in the display space.
         sp<FakeWindowHandle> firstWindow =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform);
         addWindow(firstWindow);
 
         sp<FakeWindowHandle> secondWindow =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         secondWindow->setFrame(Rect(100, 200, 200, 400), displayTransform);
         addWindow(secondWindow);
         return {std::move(firstWindow), std::move(secondWindow)};
@@ -5158,8 +6499,8 @@
     // selected so that if the hit test was performed with the point and the bounds being in
     // different coordinate spaces, the event would end up in the incorrect window.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{75, 55}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{75, 55}}));
 
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
@@ -5172,7 +6513,7 @@
     // Send down to the first window. The point is represented in the logical display space. The
     // point is selected so that if the hit test was done in logical display space, then it would
     // end up in the incorrect window.
-    injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                      PointF{75 * 2, 55 * 4});
 
     firstWindow->consumeMotionDown();
@@ -5191,7 +6532,7 @@
     const vec2 untransformedPoint = injectedEventTransform.inverse().transform(expectedPoint);
 
     MotionEvent event = MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                                .displayId(ADISPLAY_ID_DEFAULT)
+                                .displayId(ui::LogicalDisplayId::DEFAULT)
                                 .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                                 .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER)
                                                  .x(untransformedPoint.x)
@@ -5210,9 +6551,9 @@
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
 
     // Send down to the second window.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {PointF{150, 220}}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {PointF{150, 220}}));
 
     firstWindow->assertNoEvents();
     std::unique_ptr<MotionEvent> event = secondWindow->consumeMotionEvent();
@@ -5234,17 +6575,17 @@
     auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
     // The monitor will always receive events in the logical display's coordinate space, because
     // it does not have a window.
-    FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ADISPLAY_ID_DEFAULT};
+    FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ui::LogicalDisplayId::DEFAULT};
 
     // Send down to the first window.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}}));
     firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
     monitor.consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
 
     // Second pointer goes down on second window.
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {PointF{50, 100}, PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80)));
     const std::map<int32_t, PointF> expectedMonitorPointers{{0, PointF{100, 400}},
@@ -5265,7 +6606,7 @@
 
     // Send down to the first window.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {PointF{50, 100}}));
     firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
 
     // The pointer is transferred to the second window, and the second window receives it in the
@@ -5280,13 +6621,15 @@
 
     // Send hover move to the second window, and ensure it shows up as hover enter.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
     // Touch down at the same location and ensure a hover exit is synthesized.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880)));
     secondWindow->consumeMotionEvent(
@@ -5311,14 +6654,16 @@
 
     // Send hover move to the second window, and ensure it shows up as hover enter.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
     // Touch down at the same location and ensure a hover exit is synthesized for the correct
     // display.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880)));
     secondWindow->consumeMotionEvent(
@@ -5332,7 +6677,8 @@
 
     // Send hover enter to second window
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880)));
 
@@ -5360,17 +6706,18 @@
 
     // Send hover enter to second window
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
-                                                 ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {PointF{150, 220}}));
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
                                            WithCoords(100, 80), WithRawCoords(300, 880),
-                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 
     mDispatcher->cancelCurrentTouch();
 
     // Ensure the cancelation happens with the correct displayId and the correct coordinates.
     secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
                                            WithRawCoords(300, 880),
-                                           WithDisplayId(ADISPLAY_ID_DEFAULT)));
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     secondWindow->assertNoEvents();
     firstWindow->assertNoEvents();
 }
@@ -5397,13 +6744,13 @@
     const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
     const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
                                          logicalDisplayWidth, logicalDisplayHeight);
-    addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+    addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
 
     // Create a window with its bounds determined in the logical display.
     const Rect frameInLogicalDisplay(100, 100, 200, 300);
     const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay);
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(frameInDisplay, displayTransform);
     addWindow(window);
 
@@ -5413,14 +6760,14 @@
     for (const auto pointInsideWindow : insidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
         window->consumeMotionDown();
 
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
         window->consumeMotionUp();
     }
 
@@ -5430,17 +6777,105 @@
     for (const auto pointOutsideWindow : outsidePoints) {
         const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
         const PointF pointInDisplaySpace{p.x, p.y};
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
 
-        mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
     }
     window->assertNoEvents();
 }
 
+// This test verifies the occlusion detection for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, BlockUntrustClickInDifferentOrientations) {
+    constexpr static int32_t displayWidth = 400;
+    constexpr static int32_t displayHeight = 800;
+
+    std::shared_ptr<FakeApplicationHandle> untrustedWindowApplication =
+            std::make_shared<FakeApplicationHandle>();
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    const auto rotation = GetParam();
+
+    // Set up the display with the specified rotation.
+    const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+    const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+    const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+    const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+                                         logicalDisplayWidth, logicalDisplayHeight);
+    addDisplayInfo(ui::LogicalDisplayId::DEFAULT, displayTransform);
+
+    // Create a window that not trusted.
+    const Rect untrustedWindowFrameInLogicalDisplay(100, 100, 200, 300);
+
+    const Rect untrustedWindowFrameInDisplay =
+            displayTransform.inverse().transform(untrustedWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> untrustedWindow =
+            sp<FakeWindowHandle>::make(untrustedWindowApplication, mDispatcher, "UntrustedWindow",
+                                       ui::LogicalDisplayId::DEFAULT);
+    untrustedWindow->setFrame(untrustedWindowFrameInDisplay, displayTransform);
+    untrustedWindow->setTrustedOverlay(false);
+    untrustedWindow->setTouchOcclusionMode(TouchOcclusionMode::BLOCK_UNTRUSTED);
+    untrustedWindow->setTouchable(false);
+    untrustedWindow->setAlpha(1.0f);
+    untrustedWindow->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+    addWindow(untrustedWindow);
+
+    // Create a simple app window below the untrusted window.
+    const Rect simpleAppWindowFrameInLogicalDisplay(0, 0, 300, 600);
+    const Rect simpleAppWindowFrameInDisplay =
+            displayTransform.inverse().transform(simpleAppWindowFrameInLogicalDisplay);
+
+    sp<FakeWindowHandle> simpleAppWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "SimpleAppWindow",
+                                       ui::LogicalDisplayId::DEFAULT);
+    simpleAppWindow->setFrame(simpleAppWindowFrameInDisplay, displayTransform);
+    simpleAppWindow->setOwnerInfo(gui::Pid{2}, gui::Uid{202});
+    addWindow(simpleAppWindow);
+
+    // The following points in logical display space should be inside the untrusted window, so
+    // the simple window could not receive events that coordinate is these point.
+    static const std::array<vec2, 4> untrustedPoints{
+            {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+
+    for (const auto untrustedPoint : untrustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(untrustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+    }
+    untrustedWindow->assertNoEvents();
+    simpleAppWindow->assertNoEvents();
+    // The following points in logical display space should be outside the untrusted window, so
+    // the simple window should receive events that coordinate is these point.
+    static const std::array<vec2, 5> trustedPoints{
+            {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+    for (const auto trustedPoint : trustedPoints) {
+        const vec2 p = displayTransform.inverse().transform(trustedPoint);
+        const PointF pointInDisplaySpace{p.x, p.y};
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+        mDispatcher->notifyMotion(
+                generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, {pointInDisplaySpace}));
+        simpleAppWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                         AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+    }
+    untrustedWindow->assertNoEvents();
+}
+
 // Run the precision tests for all rotations.
 INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
                          InputDispatcherDisplayOrientationFixture,
@@ -5462,13 +6897,14 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> wallpaper =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper->setIsWallpaper(true);
     // Add the windows to the dispatcher, and ensure the first window is focused
     mDispatcher->onWindowInfosChanged(
@@ -5478,12 +6914,13 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     // Dispatcher reports pointer down outside focus for the wallpaper
     mFakePolicy->assertOnPointerDownEquals(wallpaper->getToken());
 
@@ -5493,17 +6930,19 @@
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
-    wallpaper->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    wallpaper->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     // There should not be any changes to the focused window when transferring touch
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertOnPointerDownWasNotCalled());
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets no events and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     wallpaper->assertNoEvents();
 }
 
@@ -5523,14 +6962,15 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     // Create a couple of windows + a spy window
-    sp<FakeWindowHandle> spyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spyWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy",
+                                                                ui::LogicalDisplayId::DEFAULT);
     spyWindow->setTrustedOverlay(true);
     spyWindow->setSpy(true);
-    sp<FakeWindowHandle> firstWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> firstWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "First",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> secondWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     // Add the windows to the dispatcher
     mDispatcher->onWindowInfosChanged(
@@ -5538,7 +6978,8 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     // Only the first window and spy should get the down event
     spyWindow->consumeMotionDown();
     firstWindow->consumeMotionDown();
@@ -5550,15 +6991,17 @@
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first  window gets no events and the second+spy get up
     firstWindow->assertNoEvents();
     spyWindow->consumeMotionUp();
-    secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
@@ -5569,11 +7012,11 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setPreventSplitting(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setPreventSplitting(true);
 
     // Add the windows to the dispatcher
@@ -5582,15 +7025,16 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchPoint}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send pointer down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {touchPoint, touchPoint}));
     // Only the first window should get the pointer down event
     firstWindow->consumeMotionPointerDown(1);
     secondWindow->assertNoEvents();
@@ -5601,24 +7045,29 @@
     ASSERT_TRUE(success);
     // The first window gets cancel and the second gets down and pointer down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
-    secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT,
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT,
                                            AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send pointer up to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {touchPoint, touchPoint}));
+                                                 ui::LogicalDisplayId::DEFAULT,
+                                                 {touchPoint, touchPoint}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT,
-                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                         AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                               WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
+                                               WithPointerCount(2)));
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
@@ -5627,19 +7076,21 @@
     // Create a couple of windows
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setDupTouchToWallpaper(true);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setDupTouchToWallpaper(true);
 
     sp<FakeWindowHandle> wallpaper1 =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper1->setIsWallpaper(true);
 
     sp<FakeWindowHandle> wallpaper2 =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2",
+                                       ui::LogicalDisplayId::DEFAULT);
     wallpaper2->setIsWallpaper(true);
     // Add the windows to the dispatcher
     mDispatcher->onWindowInfosChanged({{*firstWindow->getInfo(), *wallpaper1->getInfo(),
@@ -5650,12 +7101,13 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
-    wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+    wallpaper1->consumeMotionDown(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
     wallpaper2->assertNoEvents();
 
     // Transfer touch focus to the second window
@@ -5665,20 +7117,22 @@
 
     // The first window gets cancel and the second gets down
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
-    wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
-    wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT,
-                                  expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    wallpaper1->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, EXPECTED_WALLPAPER_FLAGS);
+    wallpaper2->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                  EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first  window gets no events and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     wallpaper1->assertNoEvents();
-    wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT,
-                                expectedWallpaperFlags | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    wallpaper2->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                EXPECTED_WALLPAPER_FLAGS | AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 // For the cases of single pointer touch and two pointers non-split touch, the api's
@@ -5690,7 +7144,7 @@
                 [&](const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder> /*ignored*/,
                     sp<IBinder> destChannelToken) {
                     return dispatcher->transferTouchOnDisplay(destChannelToken,
-                                                              ADISPLAY_ID_DEFAULT);
+                                                              ui::LogicalDisplayId::DEFAULT);
                 },
                 [&](const std::unique_ptr<InputDispatcher>& dispatcher, sp<IBinder> from,
                     sp<IBinder> to) {
@@ -5703,12 +7157,12 @@
 
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
 
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
@@ -5720,15 +7174,15 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
@@ -5738,24 +7192,27 @@
     mDispatcher->transferTouchGesture(firstWindow->getToken(), secondWindow->getToken());
     // The first window gets cancel and the new gets pointer down (it already saw down)
     firstWindow->consumeMotionCancel();
-    secondWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT,
+    secondWindow->consumeMotionPointerDown(1, ui::LogicalDisplayId::DEFAULT,
                                            AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     // Send pointer up to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets nothing and the second gets pointer up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT,
-                                         AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                         AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                               WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
+                                               WithPointerCount(2)));
 
     // Send up event to the second window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->assertNoEvents();
-    secondWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                  AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 // Same as TransferTouch_TwoPointersSplitTouch, but using 'transferTouchOnDisplay' api.
@@ -5767,12 +7224,12 @@
 
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
 
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
@@ -5784,23 +7241,23 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
     secondWindow->consumeMotionDown();
 
     // Transfer touch focus to the second window
-    const bool transferred =
-            mDispatcher->transferTouchOnDisplay(secondWindow->getToken(), ADISPLAY_ID_DEFAULT);
+    const bool transferred = mDispatcher->transferTouchOnDisplay(secondWindow->getToken(),
+                                                                 ui::LogicalDisplayId::DEFAULT);
     // The 'transferTouchOnDisplay' call should not succeed, because there are 2 touched windows
     ASSERT_FALSE(transferred);
     firstWindow->assertNoEvents();
@@ -5809,7 +7266,7 @@
     // The rest of the dispatch should proceed as normal
     // Send pointer up to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets MOVE and the second gets pointer up
     firstWindow->consumeMotionMove();
@@ -5817,7 +7274,7 @@
 
     // Send up event to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets nothing and the second gets up
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
@@ -5829,13 +7286,16 @@
 TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> firstWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1",
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
-    sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> mirrorWindowInPrimary =
+            firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
 
     sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
@@ -5854,35 +7314,36 @@
              0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Window should receive motion event.
-    firstWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    firstWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Transfer touch
     ASSERT_TRUE(mDispatcher->transferTouchGesture(firstWindowInPrimary->getToken(),
                                                   secondWindowInPrimary->getToken()));
     // The first window gets cancel.
     firstWindowInPrimary->consumeMotionCancel();
-    secondWindowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+    secondWindowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
                                              AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionMove(ADISPLAY_ID_DEFAULT,
+    secondWindowInPrimary->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
                                              AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     firstWindowInPrimary->assertNoEvents();
-    secondWindowInPrimary->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    secondWindowInPrimary->consumeMotionUp(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 }
 
 // Same as TransferTouch_CloneSurface, but this touch on the secondary display and use
@@ -5890,13 +7351,16 @@
 TEST_F(InputDispatcherTest, TransferTouchOnDisplay_CloneSurface) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> firstWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1",
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
 
-    sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> mirrorWindowInPrimary =
+            firstWindowInPrimary->clone(ui::LogicalDisplayId::DEFAULT);
     mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
 
     sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
@@ -5949,8 +7413,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -5958,10 +7423,10 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Should have poked user activity
     mDispatcher->waitForIdle();
@@ -5970,8 +7435,9 @@
 
 TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
@@ -5980,20 +7446,22 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
 
     // Window should receive key down event.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
-    // Should have poked user activity
+    // Should have not poked user activity
     mDispatcher->waitForIdle();
     mFakePolicy->assertUserActivityNotPoked();
 }
 
-TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) {
+TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceivePolicyConsumedKey) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6001,41 +7469,24 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mFakePolicy->setConsumeKeyBeforeDispatching(true);
+
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
     mDispatcher->waitForIdle();
 
-    // System key is not passed down
+    // Key is not passed down
     window->assertNoEvents();
 
     // Should have poked user activity
     mFakePolicy->assertUserActivityPoked();
 }
 
-TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveAssistantKey) {
+TEST_F(InputDispatcherTest, FocusedWindow_PolicyConsumedKeyIgnoresDisableUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
-
-    window->setFocusable(true);
-    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
-    setFocusedWindow(window);
-
-    window->consumeFocusEvent(true);
-
-    mDispatcher->notifyKey(generateAssistantKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
-    mDispatcher->waitForIdle();
-
-    // System key is not passed down
-    window->assertNoEvents();
-
-    // Should have poked user activity
-    mFakePolicy->assertUserActivityPoked();
-}
-
-TEST_F(InputDispatcherTest, FocusedWindow_SystemKeyIgnoresDisableUserActivity) {
-    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     window->setDisableUserActivity(true);
     window->setFocusable(true);
@@ -6044,7 +7495,10 @@
 
     window->consumeFocusEvent(true);
 
-    mDispatcher->notifyKey(generateSystemShortcutArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mFakePolicy->setConsumeKeyBeforeDispatching(true);
+
+    mDispatcher->notifyKey(
+            KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
     mDispatcher->waitForIdle();
 
     // System key is not passed down
@@ -6054,20 +7508,54 @@
     mFakePolicy->assertUserActivityPoked();
 }
 
+class DisableUserActivityInputDispatcherTest : public InputDispatcherTest,
+                                               public ::testing::WithParamInterface<bool> {};
+
+TEST_P(DisableUserActivityInputDispatcherTest, NotPassedToUserUserActivity) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+
+    window->setDisableUserActivity(GetParam());
+
+    window->setFocusable(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+
+    window->consumeFocusEvent(true);
+
+    mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                   .keyCode(AKEYCODE_A)
+                                   .policyFlags(0)
+                                   .build());
+    mDispatcher->waitForIdle();
+
+    // Key is not passed down
+    window->assertNoEvents();
+
+    // Should not have poked user activity
+    mFakePolicy->assertUserActivityNotPoked();
+}
+
+INSTANTIATE_TEST_CASE_P(DisableUserActivity, DisableUserActivityInputDispatcherTest,
+                        ::testing::Bool());
+
 TEST_F(InputDispatcherTest, InjectedTouchesPokeUserActivity) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {100, 100}))
+                                ui::LogicalDisplayId::DEFAULT, {100, 100}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
 
     // Should have poked user activity
     mDispatcher->waitForIdle();
@@ -6076,12 +7564,13 @@
 
 TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
 
     window->assertNoEvents();
@@ -6090,19 +7579,21 @@
 // If a window is touchable, but does not have focus, it should receive motion events, but not keys
 TEST_F(InputDispatcherTest, UnfocusedWindow_ReceivesMotionsButNotKeys) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     // Send key
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     // Send motion
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
 
     // Window should receive only the motion event
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents(); // Key event or focus event will not be received
 }
 
@@ -6111,12 +7602,12 @@
 
     sp<FakeWindowHandle> firstWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     firstWindow->setFrame(Rect(0, 0, 600, 400));
 
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindow->setFrame(Rect(0, 400, 600, 800));
 
     // Add the windows to the dispatcher
@@ -6128,15 +7619,15 @@
 
     // Send down to the first window
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {pointInFirst}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {pointInFirst}));
     // Only the first window should get the down event
     firstWindow->consumeMotionDown();
     secondWindow->assertNoEvents();
 
     // Send down to the second window
     mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT,
+                                                 ui::LogicalDisplayId::DEFAULT,
                                                  {pointInFirst, pointInSecond}));
     // The first window gets a move and the second a down
     firstWindow->consumeMotionMove();
@@ -6144,17 +7635,17 @@
 
     // Send pointer cancel to the second window
     NotifyMotionArgs pointerUpMotionArgs =
-            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {pointInFirst, pointInSecond});
+            generateMotionArgs(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {pointInFirst, pointInSecond});
     pointerUpMotionArgs.flags |= AMOTION_EVENT_FLAG_CANCELED;
     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);
+    firstWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    secondWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
 
     // Send up event.
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     // The first window gets up and the second gets nothing.
     firstWindow->consumeMotionUp();
     secondWindow->assertNoEvents();
@@ -6163,8 +7654,8 @@
 TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
@@ -6187,28 +7678,29 @@
  */
 TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     // Both the foreground window and the global monitor should receive the touch down
     window->consumeMotionDown();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     window->consumeMotionMove();
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     // Now the foreground window goes away
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -6219,41 +7711,47 @@
     // cause a cancel for the monitor, as well.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {120, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {120, 200}))
             << "Injection should fail because the window was removed";
     window->assertNoEvents();
     // Global monitor now gets the cancel
-    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Pilfer pointers from the monitor.
     // This should not do anything and the window should continue to receive events.
@@ -6261,27 +7759,30 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT))
+                                ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
-    window->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
+    window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     window->setWindowOffset(20, 40);
     window->setWindowTransform(0, 1, -1, 0);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     std::unique_ptr<MotionEvent> event = monitor.consumeMotion();
     ASSERT_NE(nullptr, event);
     // Even though window has transform, gesture monitor must not.
@@ -6290,10 +7791,12 @@
 
 TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Injection should fail if there is a monitor, but no touchable window";
     monitor.assertNoEvents();
 }
@@ -6311,20 +7814,21 @@
  */
 TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCanceledWhenAnotherEmptyDisplayReceiveEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "The down event injected into the first display should succeed";
 
     window->consumeMotionDown();
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
@@ -6335,19 +7839,19 @@
     // Continue to inject event to first display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 220}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
             << "The move event injected into the first display should succeed";
 
     window->consumeMotionMove();
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {110, 220}))
             << "The up event injected into the first display should succeed";
 
     window->consumeMotionUp();
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     window->assertNoEvents();
     monitor.assertNoEvents();
@@ -6371,24 +7875,25 @@
  */
 TEST_F(InputDispatcherMonitorTest, MonitorTouchIsNotCancelWhenAnotherDisplayMonitorTouchCanceled) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> secondWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "SecondForeground",
                                        SECOND_DISPLAY_ID);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver secondMonitor = FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // There is a foreground window on both displays.
     mDispatcher->onWindowInfosChanged({{*window->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "The down event injected into the first display should succeed";
 
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
@@ -6416,11 +7921,11 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "The move event injected into the first display should succeed";
 
     window->consumeMotionMove();
-    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
@@ -6429,12 +7934,12 @@
                "touchable window";
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {110, 220}))
             << "The up event injected into the first display should succeed";
 
-    window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
-    monitor.consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
+    monitor.consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     window->assertNoEvents();
     monitor.assertNoEvents();
@@ -6452,22 +7957,23 @@
  */
 TEST_F(InputDispatcherMonitorTest, MonitorTouchCancelEventWithDisplayTransform) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ui::Transform transform;
     transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
 
     gui::DisplayInfo displayInfo;
-    displayInfo.displayId = ADISPLAY_ID_DEFAULT;
+    displayInfo.displayId = ui::LogicalDisplayId::DEFAULT;
     displayInfo.transform = transform;
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "The down event injected should succeed";
 
     window->consumeMotionDown();
@@ -6477,7 +7983,7 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 220}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
             << "The move event injected should succeed";
 
     window->consumeMotionMove();
@@ -6493,19 +7999,19 @@
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 220}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
             << "The move event injected should failed";
     // Now foreground should not receive any events, but monitor should receive a cancel event
     // with transform that same as display's display.
     std::unique_ptr<MotionEvent> cancelMotionEvent = monitor.consumeMotion();
     EXPECT_EQ(transform, cancelMotionEvent->getTransform());
-    EXPECT_EQ(ADISPLAY_ID_DEFAULT, cancelMotionEvent->getDisplayId());
+    EXPECT_EQ(ui::LogicalDisplayId::DEFAULT, cancelMotionEvent->getDisplayId());
     EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, cancelMotionEvent->getAction());
 
     // Other event inject to this display should fail.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 220}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 220}))
             << "The up event injected should fail because the touched window was removed";
     window->assertNoEvents();
     monitor.assertNoEvents();
@@ -6513,18 +8019,19 @@
 
 TEST_F(InputDispatcherTest, TestMoveEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Fake Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
     NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
+                               ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(motionArgs);
     // Window should receive motion down event.
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     motionArgs.action = AMOTION_EVENT_ACTION_MOVE;
     motionArgs.id += 1;
@@ -6533,7 +8040,7 @@
                                              motionArgs.pointerCoords[0].getX() - 10);
 
     mDispatcher->notifyMotion(motionArgs);
-    window->consumeMotionMove(ADISPLAY_ID_DEFAULT, /*expectedFlags=*/0);
+    window->consumeMotionMove(ui::LogicalDisplayId::DEFAULT, /*expectedFlags=*/0);
 }
 
 /**
@@ -6543,12 +8050,13 @@
  */
 TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     const WindowInfo& windowInfo = *window->getInfo();
 
     // Set focused application.
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
 
     SCOPED_TRACE("Check default value of touch mode");
@@ -6563,7 +8071,7 @@
 
     SCOPED_TRACE("Disable touch mode");
     mDispatcher->setInTouchMode(false, windowInfo.ownerPid, windowInfo.ownerUid,
-                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT);
     window->consumeTouchModeEvent(false);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6577,7 +8085,7 @@
 
     SCOPED_TRACE("Enable touch mode again");
     mDispatcher->setInTouchMode(true, windowInfo.ownerPid, windowInfo.ownerUid,
-                                /*hasPermission=*/true, ADISPLAY_ID_DEFAULT);
+                                /*hasPermission=*/true, ui::LogicalDisplayId::DEFAULT);
     window->consumeTouchModeEvent(true);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6589,10 +8097,11 @@
 
 TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
@@ -6623,27 +8132,39 @@
     ASSERT_EQ(keyArgs.scanCode, verifiedKey.scanCode);
     ASSERT_EQ(keyArgs.metaState, verifiedKey.metaState);
     ASSERT_EQ(0, verifiedKey.repeatCount);
+
+    // InputEvent and subclasses don't have a virtual destructor and only
+    // InputEvent's destructor gets called when `verified` goes out of scope,
+    // even if `verifyInputEvent` returns an object of a subclass.  To fix this,
+    // we should either consider using std::variant in some way, or introduce an
+    // intermediate POD data structure that we will put the data into just prior
+    // to signing.  Adding virtual functions to these classes is undesirable as
+    // the bytes in these objects are getting signed.  As a temporary fix, cast
+    // the pointer to the correct class (which is statically known) before
+    // destruction.
+    delete (VerifiedKeyEvent*)verified.release();
 }
 
 TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
 
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     ui::Transform transform;
     transform.set({1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0, 0, 1});
 
     gui::DisplayInfo displayInfo;
-    displayInfo.displayId = ADISPLAY_ID_DEFAULT;
+    displayInfo.displayId = ui::LogicalDisplayId::DEFAULT;
     displayInfo.transform = transform;
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {displayInfo}, 0, 0});
 
     const NotifyMotionArgs motionArgs =
             generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                               ADISPLAY_ID_DEFAULT);
+                               ui::LogicalDisplayId::DEFAULT);
     mDispatcher->notifyMotion(motionArgs);
 
     std::unique_ptr<MotionEvent> event = window->consumeMotionEvent();
@@ -6669,6 +8190,10 @@
     EXPECT_EQ(motionArgs.downTime, verifiedMotion.downTimeNanos);
     EXPECT_EQ(motionArgs.metaState, verifiedMotion.metaState);
     EXPECT_EQ(motionArgs.buttonState, verifiedMotion.buttonState);
+
+    // Cast to the correct type before destruction.  See explanation at the end
+    // of the VerifyInputEvent_KeyEvent test.
+    delete (VerifiedMotionEvent*)verified.release();
 }
 
 /**
@@ -6703,7 +8228,7 @@
     verifiedEvent.eventTimeNanos += 1;
     ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent));
 
-    verifiedEvent.displayId += 1;
+    verifiedEvent.displayId = ui::LogicalDisplayId{verifiedEvent.displayId.val() + 1};
     ASSERT_NE(initialHmac, mDispatcher->sign(verifiedEvent));
 
     verifiedEvent.action += 1;
@@ -6730,11 +8255,12 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     // Top window is also focusable but is not granted focus.
     windowTop->setFocusable(true);
@@ -6748,15 +8274,15 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
-    windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     windowTop->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     window->setFocusable(true);
     // Release channel for window is no longer valid.
@@ -6773,10 +8299,10 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFocusable(false);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
@@ -6790,11 +8316,12 @@
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> windowTop =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     windowTop->setFocusable(true);
     windowSecond->setFocusable(true);
@@ -6813,16 +8340,17 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Focused window should receive event.
-    windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowSecond->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 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);
+    sp<FakeWindowHandle> windowTop = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                                ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> windowSecond =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     windowTop->setFocusable(true);
     windowSecond->setFocusable(false);
@@ -6836,18 +8364,18 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
 
     // Event should be dropped.
-    windowTop->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowTop->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     windowSecond->assertNoEvents();
 }
 
 TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
+                                                             ui::LogicalDisplayId::DEFAULT);
     sp<FakeWindowHandle> previousFocusedWindow =
             sp<FakeWindowHandle>::make(application, mDispatcher, "previousFocusedWindow",
-                                       ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+                                       ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     window->setFocusable(true);
     previousFocusedWindow->setFocusable(true);
@@ -6864,7 +8392,7 @@
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+                        ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE));
 
     // Window does not get focus event or key down.
     window->assertNoEvents();
@@ -6876,14 +8404,14 @@
     // Window receives focus event.
     window->consumeFocusEvent(true);
     // Focused window receives key down.
-    window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherTest, DisplayRemoved) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     // window is granted focus.
     window->setFocusable(true);
@@ -6892,7 +8420,7 @@
     window->consumeFocusEvent(true);
 
     // When a display is removed window loses focus.
-    mDispatcher->displayRemoved(ADISPLAY_ID_DEFAULT);
+    mDispatcher->displayRemoved(ui::LogicalDisplayId::DEFAULT);
     window->consumeFocusEvent(false);
 }
 
@@ -6924,10 +8452,11 @@
     constexpr gui::Uid SLIPPERY_UID{WINDOW_UID.val() + 1};
 
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
     sp<FakeWindowHandle> slipperyExitWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                       ui::LogicalDisplayId::DEFAULT);
     slipperyExitWindow->setSlippery(true);
     // Make sure this one overlaps the bottom window
     slipperyExitWindow->setFrame(Rect(25, 25, 75, 75));
@@ -6936,7 +8465,8 @@
     slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID);
 
     sp<FakeWindowHandle> slipperyEnterWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                       ui::LogicalDisplayId::DEFAULT);
     slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
 
     mDispatcher->onWindowInfosChanged(
@@ -6944,20 +8474,20 @@
 
     // Use notifyMotion instead of injecting to avoid dealing with injection permissions
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {{50, 50}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {{50, 50}}));
     slipperyExitWindow->consumeMotionDown();
     slipperyExitWindow->setFrame(Rect(70, 70, 100, 100));
     mDispatcher->onWindowInfosChanged(
             {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_MOVE,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {{51, 51}}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {{51, 51}}));
 
     slipperyExitWindow->consumeMotionCancel();
 
-    slipperyEnterWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT,
+    slipperyEnterWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
                                            AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
 }
 
@@ -6972,12 +8502,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> leftSlipperyWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                       ui::LogicalDisplayId::DEFAULT);
     leftSlipperyWindow->setSlippery(true);
     leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100));
 
     sp<FakeWindowHandle> rightDropTouchesWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                       ui::LogicalDisplayId::DEFAULT);
     rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100));
     rightDropTouchesWindow->setDropInput(true);
 
@@ -7008,12 +8540,14 @@
 TEST_F(InputDispatcherTest, InjectedTouchSlips) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> originalWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Original", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Original",
+                                       ui::LogicalDisplayId::DEFAULT);
     originalWindow->setFrame(Rect(0, 0, 200, 200));
     originalWindow->setSlippery(true);
 
     sp<FakeWindowHandle> appearingWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing",
+                                       ui::LogicalDisplayId::DEFAULT);
     appearingWindow->setFrame(Rect(0, 0, 200, 200));
 
     mDispatcher->onWindowInfosChanged({{*originalWindow->getInfo()}, {}, 0, 0});
@@ -7048,24 +8582,175 @@
     appearingWindow->assertNoEvents();
 }
 
+/**
+ * Three windows:
+ * - left window, which has FLAG_SLIPPERY, so it supports slippery exit
+ * - right window
+ * - spy window
+ * The three windows do not overlap.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN, which lands in the left window
+ * - Device B reports ACTION_DOWN, which lands in the spy window.
+ * - Now, device B reports ACTION_MOVE events which move to the right window.
+ *
+ * The right window should not receive any events because the spy window is not a foreground window,
+ * and also it does not support slippery touches.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSpyWindowSlipTest) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(200, 0, 300, 100));
+    spyWindow->setSpy(true);
+    spyWindow->setTrustedOverlay(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *rightWindow->getInfo(), *spyWindow->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on spy window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to right window with device B. Touches should not slip to the right window, because spy
+    // window is not a foreground window, and it does not have FLAG_SLIPPERY
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+    spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+}
+
+/**
+ * Three windows arranged horizontally and without any overlap.
+ * The left and right windows have FLAG_SLIPPERY. The middle window does not have any special flags.
+ *
+ * We have two devices reporting events:
+ * - Device A reports ACTION_DOWN which lands in the left window
+ * - Device B reports ACTION_DOWN which lands in the right window
+ * - Device B reports ACTION_MOVE that shifts to the middle window.
+ * This should cause touches for Device B to slip from the right window to the middle window.
+ * The right window should receive ACTION_CANCEL for device B and the
+ * middle window should receive down event for Device B.
+ * If device B reports more ACTION_MOVE events, the middle window should receive remaining events.
+ */
+TEST_F(InputDispatcherTest, MultiDeviceSlipperyWindowTest) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Left window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+    leftWindow->setSlippery(true);
+
+    sp<FakeWindowHandle> middleWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "middle window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    middleWindow->setFrame(Rect(100, 0, 200, 100));
+
+    sp<FakeWindowHandle> rightWindow =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 300, 100));
+    rightWindow->setSlippery(true);
+
+    mDispatcher->onWindowInfosChanged(
+            {{*leftWindow->getInfo(), *middleWindow->getInfo(), *rightWindow->getInfo()},
+             {},
+             0,
+             0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    // Tap on left window with device A
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Tap on right window with device B
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(250).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device B. Touches should slip to middle window, because right
+    // window is a foreground window that's associated with device B and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceB)
+                                      .build());
+    rightWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceB)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB)));
+
+    // Move to middle window with device A. Touches should slip to middle window, because left
+    // window is a foreground window that's associated with device A and has FLAG_SLIPPERY.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    leftWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(deviceA)));
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    // Ensure that middle window can receive the remaining move events.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .deviceId(deviceB)
+                                      .build());
+    leftWindow->assertNoEvents();
+    middleWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceB)));
+    rightWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
     using Uid = gui::Uid;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> leftWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> leftWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Left",
+                                                                 ui::LogicalDisplayId::DEFAULT);
     leftWindow->setFrame(Rect(0, 0, 100, 100));
     leftWindow->setOwnerInfo(gui::Pid{1}, Uid{101});
 
     sp<FakeWindowHandle> rightSpy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy",
+                                       ui::LogicalDisplayId::DEFAULT);
     rightSpy->setFrame(Rect(100, 0, 200, 100));
     rightSpy->setOwnerInfo(gui::Pid{2}, Uid{102});
     rightSpy->setSpy(true);
     rightSpy->setTrustedOverlay(true);
 
-    sp<FakeWindowHandle> rightWindow =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> rightWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Right",
+                                                                  ui::LogicalDisplayId::DEFAULT);
     rightWindow->setFrame(Rect(100, 0, 200, 100));
     rightWindow->setOwnerInfo(gui::Pid{3}, Uid{103});
 
@@ -7130,8 +8815,8 @@
 TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
-    sp<FakeWindowHandle> window =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
     window->setFrame(Rect(0, 0, 100, 100));
     window->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
 
@@ -7140,14 +8825,14 @@
     ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true));
 
     mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build());
-    ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT));
+    ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     ASSERT_NO_FATAL_FAILURE(
             mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {gui::Uid{101}}));
 
     // The UP actions are not treated as device interaction.
     mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build());
-    ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT));
+    ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
 }
@@ -7156,13 +8841,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -7171,8 +8857,9 @@
             {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
 
     // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId.
-    NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
-                                                     ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+    NotifyMotionArgs notifyArgs =
+            generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                               ui::LogicalDisplayId::DEFAULT, {PointF{50, 50}});
     mDispatcher->notifyMotion(notifyArgs);
 
     std::unique_ptr<MotionEvent> leftEnter = left->consumeMotionEvent(
@@ -7185,8 +8872,8 @@
                                   WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
 
     // Send move to the right window, and ensure hover exit and enter are synthesized with new ids.
-    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
-                                    {PointF{150, 50}});
+    notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+                                    ui::LogicalDisplayId::DEFAULT, {PointF{150, 50}});
     mDispatcher->notifyMotion(notifyArgs);
 
     std::unique_ptr<MotionEvent> leftExit = left->consumeMotionEvent(
@@ -7201,6 +8888,61 @@
     spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
 }
 
+/**
+ * When a device reports a DOWN event, which lands in a window that supports splits, and then the
+ * device then reports a POINTER_DOWN, which lands in the location of a non-existing window, then
+ * the previous window should receive this event and not be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SingleDevicePointerDownEventRetentionWithoutWindowTarget) {
+    SCOPED_FLAG_OVERRIDE(split_all_touches, false);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+                                      .build());
+
+    window->consumeMotionEvent(AllOf(WithMotionAction(POINTER_1_DOWN)));
+}
+
+/**
+ * When deviceA reports a DOWN event, which lands in a window that supports splits, and then deviceB
+ * also reports a DOWN event, which lands in the location of a non-existing window, then the
+ * previous window should receive deviceB's event and it should be dropped.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, SecondDeviceDownEventDroppedWithoutWindowTarget) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher, "Window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    const DeviceId deviceA = 9;
+    const DeviceId deviceB = 3;
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .deviceId(deviceA)
+                                      .build());
+    window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(200).y(200))
+                                      .deviceId(deviceB)
+                                      .build());
+    window->assertNoEvents();
+}
+
 class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
 protected:
     std::shared_ptr<FakeApplicationHandle> mApp;
@@ -7211,7 +8953,8 @@
 
         mApp = std::make_shared<FakeApplicationHandle>();
 
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
 
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -7510,6 +9253,7 @@
 protected:
     static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
     static constexpr std::chrono::nanoseconds KEY_REPEAT_DELAY = 40ms;
+    static constexpr bool KEY_REPEAT_ENABLED = true;
 
     std::shared_ptr<FakeApplicationHandle> mApp;
     sp<FakeWindowHandle> mWindow;
@@ -7517,13 +9261,15 @@
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
-        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
+        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY,
+                                               KEY_REPEAT_ENABLED);
         setUpWindow();
     }
 
     void setUpWindow() {
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window",
+                                             ui::LogicalDisplayId::DEFAULT);
 
         mWindow->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -7532,13 +9278,14 @@
     }
 
     void sendAndConsumeKeyDown(int32_t deviceId) {
-        NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+        NotifyKeyArgs keyArgs =
+                generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Otherwise it won't generate repeat event
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+        mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     }
 
     void expectKeyRepeatOnce(int32_t repeatCount) {
@@ -7548,15 +9295,23 @@
     }
 
     void sendAndConsumeKeyUp(int32_t deviceId) {
-        NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+        NotifyKeyArgs keyArgs =
+                generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT);
         keyArgs.deviceId = deviceId;
         keyArgs.policyFlags |= POLICY_FLAG_TRUSTED; // Unless it won't generate repeat event
         mDispatcher->notifyKey(keyArgs);
 
         // Window should receive key down event.
-        mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
+        mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT,
                               /*expectedFlags=*/0);
     }
+
+    void injectKeyRepeat(int32_t repeatCount) {
+        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                  injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, repeatCount,
+                            ui::LogicalDisplayId::DEFAULT))
+                << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
+    }
 };
 
 TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_ReceivesKeyRepeat) {
@@ -7615,7 +9370,7 @@
     sendAndConsumeKeyDown(DEVICE_ID);
     expectKeyRepeatOnce(/*repeatCount=*/1);
     mDispatcher->notifyDeviceReset({/*id=*/10, /*eventTime=*/20, DEVICE_ID});
-    mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT,
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT,
                           AKEY_EVENT_FLAG_CANCELED | AKEY_EVENT_FLAG_LONG_PRESS);
     mWindow->assertNoEvents();
 }
@@ -7645,6 +9400,35 @@
     }
 }
 
+TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_CorrectRepeatCountWhenInjectKeyRepeat) {
+    injectKeyRepeat(0);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    for (int32_t repeatCount = 1; repeatCount <= 2; ++repeatCount) {
+        expectKeyRepeatOnce(repeatCount);
+    }
+    injectKeyRepeat(1);
+    // Expect repeatCount to be 3 instead of 1
+    expectKeyRepeatOnce(3);
+}
+
+TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_NoRepeatWhenKeyRepeatDisabled) {
+    SCOPED_FLAG_OVERRIDE(keyboard_repeat_keys, true);
+    static constexpr std::chrono::milliseconds KEY_NO_REPEAT_ASSERTION_TIMEOUT = 100ms;
+
+    mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY,
+                                           /*repeatKeyEnabled=*/false);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
+
+    ASSERT_GT(KEY_NO_REPEAT_ASSERTION_TIMEOUT, KEY_REPEAT_TIMEOUT)
+            << "Ensure the check for no key repeats extends beyond the repeat timeout duration.";
+    ASSERT_GT(KEY_NO_REPEAT_ASSERTION_TIMEOUT, KEY_REPEAT_DELAY)
+            << "Ensure the check for no key repeats extends beyond the repeat delay duration.";
+
+    // No events should be returned if key repeat is turned off.
+    // Wait for KEY_NO_REPEAT_ASSERTION_TIMEOUT to return no events to ensure key repeat disabled.
+    mWindow->assertNoEvents(KEY_NO_REPEAT_ASSERTION_TIMEOUT);
+}
+
 /* Test InputDispatcher for MultiDisplay */
 class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
 public:
@@ -7652,11 +9436,11 @@
         InputDispatcherTest::SetUp();
 
         application1 = std::make_shared<FakeApplicationHandle>();
-        windowInPrimary =
-                sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT);
+        windowInPrimary = sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1",
+                                                     ui::LogicalDisplayId::DEFAULT);
 
         // Set focus window for primary display, but focused display would be second one.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application1);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application1);
         windowInPrimary->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
@@ -7669,6 +9453,8 @@
         // Set focus to second display window.
         // Set focus display to second one.
         mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+        mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+
         // Set focus window for second display.
         mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
         windowInSecondary->setFocusable(true);
@@ -7697,9 +9483,10 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) {
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test touch down on second display.
@@ -7713,22 +9500,22 @@
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) {
     // Test inject a key down with display id specified.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
 
     // Test inject a key down without display id specified.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
-    windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 
     // Remove all windows in secondary display.
     mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
 
     // Old focus should receive a cancel event.
-    windowInSecondary->consumeKeyUp(ADISPLAY_ID_NONE, AKEY_EVENT_FLAG_CANCELED);
+    windowInSecondary->consumeKeyUp(ui::LogicalDisplayId::INVALID, AKEY_EVENT_FLAG_CANCELED);
 
     // Test inject a key down, should timeout because of no target window.
     ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
@@ -7740,16 +9527,17 @@
 // Test per-display input monitors for motion event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->assertNoEvents();
     monitorInSecondary.assertNoEvents();
 
@@ -7773,19 +9561,20 @@
     // If specific a display, it will dispatch to the focused window of particular display,
     // or it will dispatch to the focused window of focused display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL,
+                               ui::LogicalDisplayId::INVALID))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
-    windowInSecondary->consumeMotionDown(ADISPLAY_ID_NONE);
-    monitorInSecondary.consumeMotionDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeMotionDown(ui::LogicalDisplayId::INVALID);
+    monitorInSecondary.consumeMotionDown(ui::LogicalDisplayId::INVALID);
 }
 
 // Test per-display input monitors for key event.
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
     // Input monitor per display.
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
@@ -7794,13 +9583,14 @@
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
-    windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
-    monitorInSecondary.consumeKeyDown(ADISPLAY_ID_NONE);
+    windowInSecondary->consumeKeyDown(ui::LogicalDisplayId::INVALID);
+    monitorInSecondary.consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) {
     sp<FakeWindowHandle> secondWindowInPrimary =
-            sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+            sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2",
+                                       ui::LogicalDisplayId::DEFAULT);
     secondWindowInPrimary->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*windowInPrimary->getInfo(), *secondWindowInPrimary->getInfo(),
@@ -7814,25 +9604,26 @@
 
     // Test inject a key down.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
     windowInPrimary->assertNoEvents();
     windowInSecondary->assertNoEvents();
-    secondWindowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    secondWindowInPrimary->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
     FakeMonitorReceiver monitorInPrimary =
-            FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
     FakeMonitorReceiver monitorInSecondary =
             FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
 
     // Test touch down on primary display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Test touch down on second display.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -7843,15 +9634,15 @@
 
     // Trigger cancel touch.
     mDispatcher->cancelCurrentTouch();
-    windowInPrimary->consumeMotionCancel(ADISPLAY_ID_DEFAULT);
-    monitorInPrimary.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    windowInPrimary->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
+    monitorInPrimary.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
     windowInSecondary->consumeMotionCancel(SECOND_DISPLAY_ID);
     monitorInSecondary.consumeMotionCancel(SECOND_DISPLAY_ID);
 
     // Test inject a move motion event, no window/monitor should receive the event.
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {110, 200}))
+                                ui::LogicalDisplayId::DEFAULT, {110, 200}))
             << "Inject motion event should return InputEventInjectionResult::FAILED";
     windowInPrimary->assertNoEvents();
     monitorInPrimary.assertNoEvents();
@@ -7874,11 +9665,11 @@
     // Send a key down on primary display
     mDispatcher->notifyKey(
             KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
                     .build());
-    windowInPrimary->consumeKeyEvent(
-            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+    windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN),
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     windowInSecondary->assertNoEvents();
 
     // Send a key down on second display
@@ -7894,7 +9685,7 @@
     // Send a valid key up event on primary display that will be dropped because it is stale
     NotifyKeyArgs staleKeyUp =
             KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
                     .build();
     static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
@@ -7906,7 +9697,7 @@
     // Therefore, windowInPrimary should get the cancel event and windowInSecondary should not
     // receive any events.
     windowInPrimary->consumeKeyEvent(AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP),
-                                           WithDisplayId(ADISPLAY_ID_DEFAULT),
+                                           WithDisplayId(ui::LogicalDisplayId::DEFAULT),
                                            WithFlags(AKEY_EVENT_FLAG_CANCELED)));
     windowInSecondary->assertNoEvents();
 }
@@ -7919,10 +9710,10 @@
     mDispatcher->notifyMotion(
             MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .build());
     windowInPrimary->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     windowInSecondary->assertNoEvents();
 
     // Send touch down on second display.
@@ -7938,7 +9729,7 @@
     // inject a valid MotionEvent on primary display that will be stale when it arrives.
     NotifyMotionArgs staleMotionUp =
             MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .build();
     static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 10ms;
@@ -7954,7 +9745,7 @@
 
 class InputFilterTest : public InputDispatcherTest {
 protected:
-    void testNotifyMotion(int32_t displayId, bool expectToBeFiltered,
+    void testNotifyMotion(ui::LogicalDisplayId displayId, bool expectToBeFiltered,
                           const ui::Transform& transform = ui::Transform()) {
         NotifyMotionArgs motionArgs;
 
@@ -7993,19 +9784,19 @@
 // Test InputFilter for MotionEvent
 TEST_F(InputFilterTest, MotionEvent_InputFilter) {
     // Since the InputFilter is disabled by default, check if touch events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 
     // Enable InputFilter
     mDispatcher->setInputFilterEnabled(true);
     // Test touch on both primary and second display, and check if both events are filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true);
 
     // Disable InputFilter
     mDispatcher->setInputFilterEnabled(false);
     // Test touch on both primary and second display, and check if both events aren't filtered.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/false);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
 }
 
@@ -8034,7 +9825,7 @@
     secondDisplayTransform.set({-6.6, -5.5, -4.4, -3.3, -2.2, -1.1, 0, 0, 1});
 
     std::vector<gui::DisplayInfo> displayInfos(2);
-    displayInfos[0].displayId = ADISPLAY_ID_DEFAULT;
+    displayInfos[0].displayId = ui::LogicalDisplayId::DEFAULT;
     displayInfos[0].transform = firstDisplayTransform;
     displayInfos[1].displayId = SECOND_DISPLAY_ID;
     displayInfos[1].transform = secondDisplayTransform;
@@ -8045,7 +9836,8 @@
     mDispatcher->setInputFilterEnabled(true);
 
     // Ensure the correct transforms are used for the displays.
-    testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true, firstDisplayTransform);
+    testNotifyMotion(ui::LogicalDisplayId::DEFAULT, /*expectToBeFiltered=*/true,
+                     firstDisplayTransform);
     testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true, secondDisplayTransform);
 }
 
@@ -8064,9 +9856,9 @@
         std::shared_ptr<InputApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
         mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Test Window",
-                                             ADISPLAY_ID_DEFAULT);
+                                             ui::LogicalDisplayId::DEFAULT);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mWindow->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
@@ -8079,8 +9871,8 @@
 
         const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
         event.initialize(InputEvent::nextId(), injectedDeviceId, AINPUT_SOURCE_KEYBOARD,
-                         ADISPLAY_ID_NONE, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0, AKEYCODE_A,
-                         KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
+                         ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, 0,
+                         AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime, eventTime);
         const int32_t additionalPolicyFlags =
                 POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -8160,20 +9952,20 @@
                 std::make_shared<FakeApplicationHandle>();
         application->setDispatchingTimeout(100ms);
         mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow",
-                                             ADISPLAY_ID_DEFAULT);
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
         mWindow->setDispatchingTimeout(100ms);
         mWindow->setFocusable(true);
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
 
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
         mWindow->consumeFocusEvent(true);
     }
 
-    void notifyAndConsumeMotion(int32_t action, uint32_t source, int32_t displayId,
+    void notifyAndConsumeMotion(int32_t action, uint32_t source, ui::LogicalDisplayId displayId,
                                 nsecs_t eventTime) {
         mDispatcher->notifyMotion(MotionArgsBuilder(action, source)
                                           .displayId(displayId)
@@ -8194,50 +9986,55 @@
     mDispatcher->setMinTimeBetweenUserActivityPokes(50ms);
 
     // First event of type TOUCH. Should poke.
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(50));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(50), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
 
     // 80ns > 50ns has passed since previous TOUCH event. Should poke.
-    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(130));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(130), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
 
     // First event of type OTHER. Should poke (despite being within 50ns of previous TOUCH event).
-    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
-                           milliseconds_to_nanoseconds(135));
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(135));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(135), USER_ACTIVITY_EVENT_OTHER,
+              ui::LogicalDisplayId::DEFAULT}});
 
     // Within 50ns of previous TOUCH event. Should NOT poke.
-    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(140));
     mFakePolicy->assertUserActivityNotPoked();
 
     // Within 50ns of previous OTHER event. Should NOT poke.
-    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
-                           milliseconds_to_nanoseconds(150));
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(150));
     mFakePolicy->assertUserActivityNotPoked();
 
     // Within 50ns of previous TOUCH event (which was at time 130). Should NOT poke.
     // Note that STYLUS is mapped to TOUCH user activity, since it's a pointer-type source.
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(160));
     mFakePolicy->assertUserActivityNotPoked();
 
     // 65ns > 50ns has passed since previous OTHER event. Should poke.
-    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER, ADISPLAY_ID_DEFAULT,
-                           milliseconds_to_nanoseconds(200));
+    notifyAndConsumeMotion(ACTION_SCROLL, AINPUT_SOURCE_ROTARY_ENCODER,
+                           ui::LogicalDisplayId::DEFAULT, milliseconds_to_nanoseconds(200));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_OTHER,
+              ui::LogicalDisplayId::DEFAULT}});
 
     // 170ns > 50ns has passed since previous TOUCH event. Should poke.
-    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_STYLUS, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(300));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(300), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
 
     // Assert that there's no more user activity poke event.
     mFakePolicy->assertUserActivityNotPoked();
@@ -8247,19 +10044,21 @@
         InputDispatcherUserActivityPokeTests, DefaultMinPokeTimeOf100MsUsed,
         REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
                                             rate_limit_user_activity_poke_in_dispatcher))) {
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(200));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(200), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
 
-    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(280));
     mFakePolicy->assertUserActivityNotPoked();
 
-    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+    notifyAndConsumeMotion(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                            milliseconds_to_nanoseconds(340));
     mFakePolicy->assertUserActivityPoked(
-            {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH, ADISPLAY_ID_DEFAULT}});
+            {{milliseconds_to_nanoseconds(340), USER_ACTIVITY_EVENT_TOUCH,
+              ui::LogicalDisplayId::DEFAULT}});
 }
 
 TEST_F_WITH_FLAGS(
@@ -8268,10 +10067,12 @@
                                             rate_limit_user_activity_poke_in_dispatcher))) {
     mDispatcher->setMinTimeBetweenUserActivityPokes(0ms);
 
-    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 20);
+    notifyAndConsumeMotion(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           20);
     mFakePolicy->assertUserActivityPoked();
 
-    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, 30);
+    notifyAndConsumeMotion(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
+                           30);
     mFakePolicy->assertUserActivityPoked();
 }
 
@@ -8281,16 +10082,16 @@
 
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
-        mUnfocusedWindow =
-                sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+        mUnfocusedWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Top",
+                                                      ui::LogicalDisplayId::DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
 
-        mFocusedWindow =
-                sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+        mFocusedWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Second",
+                                                    ui::LogicalDisplayId::DEFAULT);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
@@ -8318,8 +10119,8 @@
 // the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Success) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {20, 20}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mUnfocusedWindow->consumeMotionDown();
 
@@ -8332,7 +10133,7 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPointerSource) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT,
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ui::LogicalDisplayId::DEFAULT,
                                {20, 20}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
@@ -8345,9 +10146,9 @@
 // have focus. Ensure no window received the onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDownNoRepeat(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mFocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mFocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownWasNotCalled();
@@ -8358,8 +10159,8 @@
 // onPointerDownOutsideFocus callback.
 TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_TOUCH_POINT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_TOUCH_POINT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     mFocusedWindow->consumeMotionDown();
 
@@ -8378,7 +10179,8 @@
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, event))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mUnfocusedWindow->consumeAnyMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertOnPointerDownWasNotCalled();
@@ -8395,10 +10197,10 @@
         std::shared_ptr<FakeApplicationHandle> application =
                 std::make_shared<FakeApplicationHandle>();
         mWindow1 = sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window 1",
-                                              ADISPLAY_ID_DEFAULT);
+                                              ui::LogicalDisplayId::DEFAULT);
         mWindow1->setFrame(Rect(0, 0, 100, 100));
 
-        mWindow2 = mWindow1->clone(ADISPLAY_ID_DEFAULT);
+        mWindow2 = mWindow1->clone(ui::LogicalDisplayId::DEFAULT);
         mWindow2->setFrame(Rect(100, 100, 200, 200));
 
         mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
@@ -8439,7 +10241,7 @@
                                  const std::vector<PointF>& touchedPoints,
                                  std::vector<PointF> expectedPoints) {
         mDispatcher->notifyMotion(generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN,
-                                                     ADISPLAY_ID_DEFAULT, touchedPoints));
+                                                     ui::LogicalDisplayId::DEFAULT, touchedPoints));
 
         consumeMotionEvent(touchedWindow, action, expectedPoints);
     }
@@ -8586,14 +10388,14 @@
 
     // Touch down in window 1
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{50, 50}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {{50, 50}}));
     consumeMotionEvent(mWindow1, ACTION_DOWN, {{50, 50}});
 
     // Move touch to be above window 2. Even though window 1 is slippery, touch should not slip.
     // That means the gesture should continue normally, without any ACTION_CANCEL or ACTION_DOWN
     // getting generated.
     mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT, {{150, 150}}));
+                                                 ui::LogicalDisplayId::DEFAULT, {{150, 150}}));
 
     consumeMotionEvent(mWindow1, ACTION_MOVE, {{150, 150}});
 }
@@ -8628,13 +10430,13 @@
         mApplication = std::make_shared<FakeApplicationHandle>();
         mApplication->setDispatchingTimeout(100ms);
         mWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "TestWindow",
-                                             ADISPLAY_ID_DEFAULT);
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 30, 30));
         mWindow->setDispatchingTimeout(100ms);
         mWindow->setFocusable(true);
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
 
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
         setFocusedWindow(mWindow);
@@ -8665,8 +10467,8 @@
     }
 
     sp<FakeWindowHandle> addSpyWindow() {
-        sp<FakeWindowHandle> spy =
-                sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Spy",
+                                                              ui::LogicalDisplayId::DEFAULT);
         spy->setTrustedOverlay(true);
         spy->setFocusable(false);
         spy->setSpy(true);
@@ -8688,7 +10490,7 @@
 // Send a regular key and respond, which should not cause an ANR.
 TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
@@ -8699,15 +10501,16 @@
     mWindow->consumeFocusEvent(false);
 
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, CONSUME_TIMEOUT_EVENT_EXPECTED,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      CONSUME_TIMEOUT_EVENT_EXPECTED,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not go to window because we have no focused window.
     // The 'no focused window' ANR timer should start instead.
 
     // Now, the focused application goes away.
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, nullptr);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, nullptr);
     // The key should get dropped and there should be no ANR.
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -8719,8 +10522,8 @@
 // So InputDispatcher will enqueue ACTION_CANCEL event as well.
 TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
     const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
@@ -8729,7 +10532,7 @@
 
     mWindow->finishEvent(*sequenceNum);
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
 }
@@ -8753,8 +10556,8 @@
 
     // taps on the window work as normal
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -8763,8 +10566,9 @@
     // We specify the injection timeout to be smaller than the application timeout, to ensure that
     // injection times out (instead of failing).
     const InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, 50ms, /*allowKeyRepeat=*/false);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT, 50ms,
+                      /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
     const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -8787,11 +10591,13 @@
             std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count();
 
     // Define a valid key down event that is stale (too old).
-    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
-                     INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A,
-                     AMETA_NONE, /*repeatCount=*/1, eventTime, eventTime);
+    event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                     ui::LogicalDisplayId::INVALID, INVALID_HMAC, AKEY_EVENT_ACTION_DOWN,
+                     /*flags=*/0, AKEYCODE_A, KEY_A, AMETA_NONE, /*repeatCount=*/0, eventTime,
+                     eventTime);
 
-    const int32_t policyFlags = POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER;
+    const int32_t policyFlags =
+            POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_DISABLE_KEY_REPEAT;
 
     InputEventInjectionResult result =
             mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
@@ -8810,7 +10616,7 @@
 TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
     const std::chrono::duration appTimeout = 400ms;
     mApplication->setDispatchingTimeout(appTimeout);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
 
     mWindow->setFocusable(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
@@ -8822,8 +10628,9 @@
     const std::chrono::duration eventInjectionTimeout = 100ms;
     ASSERT_LT(eventInjectionTimeout, appTimeout);
     const InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::WAIT_FOR_RESULT,
+                      eventInjectionTimeout,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
             << "result=" << ftl::enum_string(result);
@@ -8871,20 +10678,20 @@
 TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) {
     nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
     injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                      ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+                      ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
                       500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
 
     // Now send ACTION_UP, with identical timestamp
     injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                      ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
+                      ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION,
                       {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                        AMOTION_EVENT_INVALID_CURSOR_POSITION},
                       500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
 
     // We have now sent down and up. Let's consume first event and then ANR on the second.
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
 }
@@ -8894,8 +10701,8 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
     mWindow->consumeMotionDown();
 
     const auto [sequenceNum, _] = spy->receiveEvent(); // ACTION_DOWN
@@ -8905,7 +10712,7 @@
 
     spy->finishEvent(*sequenceNum);
     spy->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(spy->getToken(), mWindow->getPid());
 }
@@ -8916,9 +10723,10 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT));
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
-    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher, ADISPLAY_ID_DEFAULT));
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT));
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectKeyUp(*mDispatcher, ui::LogicalDisplayId::DEFAULT));
 
     // Stuck on the ACTION_UP
     const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -8926,10 +10734,10 @@
 
     // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
-    mWindow->consumeKeyUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
@@ -8942,8 +10750,8 @@
     sp<FakeWindowHandle> spy = addSpyWindow();
 
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
     mWindow->consumeMotionDown();
     // Stuck on the ACTION_UP
@@ -8952,10 +10760,10 @@
 
     // New tap will go to the spy window, but not to the window
     tapOnWindow();
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 
-    mWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT); // still the previous motion
+    mWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT); // still the previous motion
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
     mWindow->assertNoEvents();
@@ -8965,13 +10773,14 @@
 TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
     mDispatcher->setMonitorDispatchingTimeoutForTest(SPY_TIMEOUT);
 
-    FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(*mDispatcher, "M_1", ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
     ASSERT_TRUE(consumeSeq);
 
@@ -8979,7 +10788,7 @@
                                                          MONITOR_PID);
 
     monitor.finishEvent(*consumeSeq);
-    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+    monitor.consumeMotionCancel(ui::LogicalDisplayId::DEFAULT);
 
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(monitor.getToken(), MONITOR_PID);
@@ -9018,8 +10827,8 @@
 // it.
 TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) {
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, WINDOW_LOCATION));
 
     const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
     mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(windowTimeout, mWindow);
@@ -9029,7 +10838,7 @@
     // When the ANR happened, dispatcher should abort the current event stream via ACTION_CANCEL
     mWindow->consumeMotionDown();
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     mWindow->assertNoEvents();
     mDispatcher->waitForIdle();
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
@@ -9066,7 +10875,7 @@
     std::this_thread::sleep_for(400ms);
     // if we wait long enough though, dispatcher will give up, and still send the key
     // to the focused window, even though we have not yet finished the motion event
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     mWindow->finishEvent(*downSequenceNum);
     mWindow->finishEvent(*upSequenceNum);
 }
@@ -9170,8 +10979,8 @@
 // So InputDispatcher will enqueue ACTION_CANCEL event as well.
 TEST_F(InputDispatcherSingleWindowAnr, AnrAfterWindowRemoval) {
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {WINDOW_LOCATION}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION}));
 
     const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
@@ -9188,7 +10997,7 @@
     mWindow->finishEvent(*sequenceNum);
     // The cancellation was generated when the window was removed, along with the focus event.
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     mWindow->consumeFocusEvent(false);
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), /*pid=*/std::nullopt);
@@ -9198,8 +11007,8 @@
 // notified of the unresponsive window, then remove the app window.
 TEST_F(InputDispatcherSingleWindowAnr, AnrFollowedByWindowRemoval) {
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {WINDOW_LOCATION}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {WINDOW_LOCATION}));
 
     const auto [sequenceNum, _] = mWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(sequenceNum);
@@ -9212,7 +11021,7 @@
     mWindow->finishEvent(*sequenceNum);
     // The cancellation was generated during the ANR, and the window lost focus when it was removed.
     mWindow->consumeMotionEvent(
-            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
     mWindow->consumeFocusEvent(false);
     ASSERT_TRUE(mDispatcher->waitForIdle());
     // Since the window was removed, Dispatcher does not know the PID associated with the window
@@ -9227,18 +11036,18 @@
         mApplication = std::make_shared<FakeApplicationHandle>();
         mApplication->setDispatchingTimeout(100ms);
         mUnfocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Unfocused",
-                                                      ADISPLAY_ID_DEFAULT);
+                                                      ui::LogicalDisplayId::DEFAULT);
         mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
         // Adding FLAG_WATCH_OUTSIDE_TOUCH to receive ACTION_OUTSIDE when another window is tapped
         mUnfocusedWindow->setWatchOutsideTouch(true);
 
         mFocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Focused",
-                                                    ADISPLAY_ID_DEFAULT);
+                                                    ui::LogicalDisplayId::DEFAULT);
         mFocusedWindow->setDispatchingTimeout(100ms);
         mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
 
         // Set focused application.
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
         mFocusedWindow->setFocusable(true);
 
         // Expect one focus window exist in display.
@@ -9270,11 +11079,11 @@
 private:
     void tap(const PointF& location) {
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                   location));
+                  injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                                   ui::LogicalDisplayId::DEFAULT, location));
         ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                 location));
+                  injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                                 ui::LogicalDisplayId::DEFAULT, location));
     }
 };
 
@@ -9299,7 +11108,7 @@
                                         .build()));
     mFocusedWindow->consumeMotionDown();
     mFocusedWindow->consumeMotionUp();
-    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
     // We consumed all events, so no ANR
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -9375,7 +11184,7 @@
 // At the same time, FLAG_WATCH_OUTSIDE_TOUCH targets should not receive any events.
 TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
     tapOnFocusedWindow();
-    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
     // Receive the events, but don't respond
     const auto [downEventSequenceNum, downEvent] = mFocusedWindow->receiveEvent(); // ACTION_DOWN
     ASSERT_TRUE(downEventSequenceNum);
@@ -9388,10 +11197,10 @@
     // Tap once again
     // We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION));
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              FOCUSED_WINDOW_LOCATION));
     // Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a
     // valid touch target
@@ -9412,8 +11221,8 @@
 // If you tap outside of all windows, there will not be ANR
 TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               LOCATION_OUTSIDE_ALL_WINDOWS));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, LOCATION_OUTSIDE_ALL_WINDOWS));
     ASSERT_TRUE(mDispatcher->waitForIdle());
     mFakePolicy->assertNotifyAnrWasNotCalled();
 }
@@ -9425,8 +11234,8 @@
             {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::FAILED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               FOCUSED_WINDOW_LOCATION));
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, FOCUSED_WINDOW_LOCATION));
 
     std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
     ASSERT_TRUE(mDispatcher->waitForIdle());
@@ -9466,8 +11275,9 @@
     // window even if motions are still being processed.
 
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms);
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // Key will not be sent to the window, yet, because the window is still processing events
     // and the key remains pending, waiting for the touch events to be processed.
@@ -9493,7 +11303,7 @@
 
     // Now that all queues are cleared and no backlog in the connections, the key event
     // can finally go to the newly focused "mUnfocusedWindow".
-    mUnfocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mUnfocusedWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
     mFocusedWindow->assertNoEvents();
     mUnfocusedWindow->assertNoEvents();
     mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -9504,14 +11314,15 @@
 // The other window should not be affected by that.
 TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) {
     // Touch Window 1
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {FOCUSED_WINDOW_LOCATION}));
-    mUnfocusedWindow->consumeMotionOutside(ADISPLAY_ID_DEFAULT, /*flags=*/0);
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {FOCUSED_WINDOW_LOCATION}));
+    mUnfocusedWindow->consumeMotionOutside(ui::LogicalDisplayId::DEFAULT, /*flags=*/0);
 
     // Touch Window 2
     mDispatcher->notifyMotion(
-            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+            generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT,
                                {FOCUSED_WINDOW_LOCATION, UNFOCUSED_WINDOW_LOCATION}));
 
     const std::chrono::duration timeout =
@@ -9557,7 +11368,7 @@
     std::shared_ptr<FakeApplicationHandle> focusedApplication =
             std::make_shared<FakeApplicationHandle>();
     focusedApplication->setDispatchingTimeout(300ms);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, focusedApplication);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, focusedApplication);
     // The application that owns 'mFocusedWindow' and 'mUnfocusedWindow' is not focused.
     mFocusedWindow->setFocusable(false);
 
@@ -9569,8 +11380,9 @@
     // 'focusedApplication' will get blamed if this timer completes.
     // Key will not be sent anywhere because we have no focused window. It will remain pending.
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
 
@@ -9584,9 +11396,9 @@
     std::this_thread::sleep_for(100ms);
 
     // Touch unfocused window. This should force the pending key to get dropped.
-    mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {UNFOCUSED_WINDOW_LOCATION}));
+    mDispatcher->notifyMotion(
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::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.
@@ -9639,20 +11451,20 @@
     mFakePolicy->setStaleEventTimeout(3000ms);
     sp<FakeWindowHandle> navigationBar =
             sp<FakeWindowHandle>::make(systemUiApplication, mDispatcher, "NavigationBar",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     navigationBar->setFocusable(false);
     navigationBar->setWatchOutsideTouch(true);
     navigationBar->setFrame(Rect(0, 0, 100, 100));
 
     mApplication->setDispatchingTimeout(3000ms);
     // 'mApplication' is already focused, but we call it again here to make it explicit.
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApplication);
 
     std::shared_ptr<FakeApplicationHandle> anotherApplication =
             std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> appWindow =
             sp<FakeWindowHandle>::make(anotherApplication, mDispatcher, "Another window",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     appWindow->setFocusable(false);
     appWindow->setFrame(Rect(100, 100, 200, 200));
 
@@ -9671,8 +11483,9 @@
     // Key will not be sent anywhere because we have no focused window. It will remain pending.
     // Pretend we are injecting KEYCODE_BACK, but it doesn't actually matter what key it is.
     InputEventInjectionResult result =
-            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                      InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+            injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
+                      ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                      /*injectionTimeout=*/100ms,
                       /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
 
@@ -9680,8 +11493,9 @@
     mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
                                       .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
                                       .build());
-    result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
-                       InputEventInjectionSync::NONE, /*injectionTimeout=*/100ms,
+    result = injectKey(*mDispatcher, AKEY_EVENT_ACTION_UP, /*repeatCount=*/0,
+                       ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE,
+                       /*injectionTimeout=*/100ms,
                        /*allowKeyRepeat=*/false);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
     // The key that was injected is blocking the dispatcher, so the navigation bar shouldn't be
@@ -9716,16 +11530,16 @@
         InputDispatcherTest::SetUp();
 
         mApplication = std::make_shared<FakeApplicationHandle>();
-        mNoInputWindow =
-                sp<FakeWindowHandle>::make(mApplication, mDispatcher,
-                                           "Window without input channel", ADISPLAY_ID_DEFAULT,
-                                           /*createInputChannel=*/false);
+        mNoInputWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher,
+                                                    "Window without input channel",
+                                                    ui::LogicalDisplayId::DEFAULT,
+                                                    /*createInputChannel=*/false);
         mNoInputWindow->setNoInputChannel(true);
         mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
         // It's perfectly valid for this window to not have an associated input channel
 
         mBottomWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Bottom window",
-                                                   ADISPLAY_ID_DEFAULT);
+                                                   ui::LogicalDisplayId::DEFAULT);
         mBottomWindow->setFrame(Rect(0, 0, 100, 100));
 
         mDispatcher->onWindowInfosChanged(
@@ -9742,8 +11556,8 @@
     PointF touchedPoint = {10, 10};
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchedPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     // Even though the window 'mNoInputWindow' positioned above 'mBottomWindow' does not have
@@ -9760,7 +11574,7 @@
        NoInputChannelFeature_DropsTouchesWithValidChannel) {
     mNoInputWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher,
                                                 "Window with input channel and NO_INPUT_CHANNEL",
-                                                ADISPLAY_ID_DEFAULT);
+                                                ui::LogicalDisplayId::DEFAULT);
 
     mNoInputWindow->setNoInputChannel(true);
     mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
@@ -9770,8 +11584,8 @@
     PointF touchedPoint = {10, 10};
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                 {touchedPoint}));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT, {touchedPoint}));
 
     mNoInputWindow->assertNoEvents();
     mBottomWindow->assertNoEvents();
@@ -9786,9 +11600,10 @@
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
-        mMirror = mWindow->clone(ADISPLAY_ID_DEFAULT);
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
+        mMirror = mWindow->clone(ui::LogicalDisplayId::DEFAULT);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mWindow->setFocusable(true);
         mMirror->setFocusable(true);
         mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -9803,7 +11618,7 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 // A focused & mirrored window remains focused only if the window and its mirror are both
@@ -9815,10 +11630,10 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mMirror->setFocusable(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -9840,20 +11655,20 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mMirror->setVisible(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     mWindow->setVisible(false);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -9874,20 +11689,20 @@
     mWindow->consumeFocusEvent(true);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
+    mWindow->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     // single window is removed but the window token remains focused
     mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mMirror->consumeKeyDown(ADISPLAY_ID_NONE);
+    mMirror->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mMirror->consumeKeyUp(ADISPLAY_ID_NONE);
+    mMirror->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     // Both windows are removed
     mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
@@ -9909,7 +11724,7 @@
     // Injected key goes to pending queue.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
-                        ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
+                        ui::LogicalDisplayId::DEFAULT, InputEventInjectionSync::NONE));
 
     mMirror->setVisible(true);
     mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
@@ -9917,7 +11732,7 @@
     // window gets focused
     mWindow->consumeFocusEvent(true);
     // window gets the pending key event
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 class InputDispatcherPointerCaptureTests : public InputDispatcherTest {
@@ -9929,13 +11744,14 @@
     void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFocusable(true);
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFocusable(true);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
 
@@ -9950,7 +11766,7 @@
     PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
                                                          bool enabled) {
         mDispatcher->requestPointerCapture(window->getToken(), enabled);
-        auto request = mFakePolicy->assertSetPointerCaptureCalled(enabled);
+        auto request = mFakePolicy->assertSetPointerCaptureCalled(window, enabled);
         notifyPointerCaptureChanged(request);
         window->consumeCaptureEvent(enabled);
         return request;
@@ -9983,7 +11799,7 @@
     mWindow->consumeCaptureEvent(false);
     mWindow->consumeFocusEvent(false);
     mSecondWindow->consumeFocusEvent(true);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
 
     // Ensure that additional state changes from InputReader are not sent to the window.
     notifyPointerCaptureChanged({});
@@ -10002,7 +11818,7 @@
     notifyPointerCaptureChanged(request);
 
     // Ensure that Pointer Capture is disabled.
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mWindow->consumeCaptureEvent(false);
     mWindow->assertNoEvents();
 }
@@ -10012,13 +11828,13 @@
 
     // The first window loses focus.
     setFocusedWindow(mSecondWindow);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mWindow->consumeCaptureEvent(false);
 
     // Request Pointer Capture from the second window before the notification from InputReader
     // arrives.
     mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
-    auto request = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto request = mFakePolicy->assertSetPointerCaptureCalled(mSecondWindow, true);
 
     // InputReader notifies Pointer Capture was disabled (because of the focus change).
     notifyPointerCaptureChanged({});
@@ -10033,11 +11849,11 @@
 TEST_F(InputDispatcherPointerCaptureTests, EnableRequestFollowsSequenceNumbers) {
     // App repeatedly enables and disables capture.
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto firstRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
     mDispatcher->requestPointerCapture(mWindow->getToken(), false);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto secondRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
 
     // InputReader notifies that PointerCapture has been enabled for the first request. Since the
     // first request is now stale, this should do nothing.
@@ -10054,10 +11870,10 @@
 
     // App toggles pointer capture off and on.
     mDispatcher->requestPointerCapture(mWindow->getToken(), false);
-    mFakePolicy->assertSetPointerCaptureCalled(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
 
     mDispatcher->requestPointerCapture(mWindow->getToken(), true);
-    auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(true);
+    auto enableRequest = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
 
     // InputReader notifies that the latest "enable" request was processed, while skipping over the
     // preceding "disable" request.
@@ -10109,6 +11925,58 @@
     mWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherPointerCaptureTests, MultiDisplayPointerCapture) {
+    // The default display is the focused display to begin with.
+    requestAndVerifyPointerCapture(mWindow, true);
+
+    // Move the second window to a second display, make it the focused window on that display.
+    mSecondWindow->editInfo()->displayId = SECOND_DISPLAY_ID;
+    mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
+    setFocusedWindow(mSecondWindow);
+    mSecondWindow->consumeFocusEvent(true);
+
+    mWindow->assertNoEvents();
+
+    // The second window cannot gain capture because it is not on the focused display.
+    mDispatcher->requestPointerCapture(mSecondWindow->getToken(), true);
+    mFakePolicy->assertSetPointerCaptureNotCalled();
+    mSecondWindow->assertNoEvents();
+
+    // Make the second display the focused display.
+    mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+    mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+
+    // This causes the first window to lose pointer capture, and it's unable to request capture.
+    mWindow->consumeCaptureEvent(false);
+    mFakePolicy->assertSetPointerCaptureCalled(mWindow, false);
+
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    mFakePolicy->assertSetPointerCaptureNotCalled();
+
+    // The second window is now able to gain pointer capture successfully.
+    requestAndVerifyPointerCapture(mSecondWindow, true);
+}
+
+using InputDispatcherPointerCaptureDeathTest = InputDispatcherPointerCaptureTests;
+
+TEST_F(InputDispatcherPointerCaptureDeathTest,
+       NotifyPointerCaptureChangedWithWrongTokenAbortsDispatcher) {
+    testing::GTEST_FLAG(death_test_style) = "threadsafe";
+    ScopedSilentDeath _silentDeath;
+
+    mDispatcher->requestPointerCapture(mWindow->getToken(), true);
+    auto request = mFakePolicy->assertSetPointerCaptureCalled(mWindow, true);
+
+    // Dispatch a pointer changed event with a wrong token.
+    request.window = mSecondWindow->getToken();
+    ASSERT_DEATH(
+            {
+                notifyPointerCaptureChanged(request);
+                mSecondWindow->consumeCaptureEvent(true);
+            },
+            "Unexpected requested window for Pointer Capture.");
+}
+
 class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
 protected:
     constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
@@ -10154,7 +12022,7 @@
     sp<FakeWindowHandle> getWindow(gui::Uid uid, std::string name) {
         std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
-                sp<FakeWindowHandle>::make(app, mDispatcher, name, ADISPLAY_ID_DEFAULT);
+                sp<FakeWindowHandle>::make(app, mDispatcher, name, ui::LogicalDisplayId::DEFAULT);
         // Generate an arbitrary PID based on the UID
         window->setOwnerInfo(gui::Pid{static_cast<pid_t>(1777 + (uid.val() % 10000))}, uid);
         return window;
@@ -10162,8 +12030,8 @@
 
     void touch(const std::vector<PointF>& points = {PointF{100, 200}}) {
         mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                     AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                                                     points));
+                                                     AINPUT_SOURCE_TOUCHSCREEN,
+                                                     ui::LogicalDisplayId::DEFAULT, points));
     }
 };
 
@@ -10528,20 +12396,21 @@
     void SetUp() override {
         InputDispatcherTest::SetUp();
         mApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFrame(Rect(0, 0, 100, 100));
 
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFrame(Rect(100, 0, 200, 100));
 
-        mSpyWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "SpyWindow", ADISPLAY_ID_DEFAULT);
+        mSpyWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "SpyWindow",
+                                                ui::LogicalDisplayId::DEFAULT);
         mSpyWindow->setSpy(true);
         mSpyWindow->setTrustedOverlay(true);
         mSpyWindow->setFrame(Rect(0, 0, 200, 100));
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()},
                  {},
@@ -10554,7 +12423,7 @@
             case AINPUT_SOURCE_TOUCHSCREEN:
                 ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
                           injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
-                                           ADISPLAY_ID_DEFAULT, {50, 50}))
+                                           ui::LogicalDisplayId::DEFAULT, {50, 50}))
                         << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
                 break;
             case AINPUT_SOURCE_STYLUS:
@@ -10586,9 +12455,9 @@
         }
 
         // Window should receive motion event.
-        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+        mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
         // Spy window should also receive motion event
-        mSpyWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+        mSpyWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     }
 
     // Start performing drag, we will create a drag window and transfer touch to it.
@@ -10600,8 +12469,8 @@
         }
 
         // The drag window covers the entire display
-        mDragWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
+        mDragWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow",
+                                                 ui::LogicalDisplayId::DEFAULT);
         mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}});
         mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(),
                                             *mWindow->getInfo(), *mSecondWindow->getInfo()},
@@ -10615,7 +12484,8 @@
                                                   /*isDragDrop=*/true);
         if (transferred) {
             mWindow->consumeMotionCancel();
-            mDragWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+            mDragWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                                           AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
         }
         return transferred;
     }
@@ -10627,35 +12497,38 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // Move back to original window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->consumeDragEvent(true, -50, 50);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -10690,27 +12563,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -10772,7 +12647,8 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
@@ -10784,7 +12660,8 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
@@ -10797,7 +12674,7 @@
                                         .pointer(PointerBuilder(0, ToolType::STYLUS).x(150).y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -10813,27 +12690,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->assertNoEvents();
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr);
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -10844,14 +12723,14 @@
     mWindow->setPreventSplitting(true);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(75).y(50))
@@ -10869,16 +12748,16 @@
 TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
     // First down on second window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    mSecondWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    mSecondWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     // Second down on first window.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -10887,8 +12766,8 @@
               injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    mSecondWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    mSecondWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT);
 
     // Perform drag and drop from first window.
     ASSERT_TRUE(startDrag(false));
@@ -10903,7 +12782,8 @@
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->consumeMotionMove();
 
@@ -10917,7 +12797,7 @@
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
                                 InputEventInjectionSync::WAIT_FOR_RESULT));
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->consumeMotionMove();
@@ -10956,27 +12836,29 @@
     // Move on window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {50, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
     // Move to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT, {150, 50}))
+                                ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
     // drop to another window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -10994,7 +12876,8 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(false, 50, 50);
     mSecondWindow->assertNoEvents();
 
@@ -11008,7 +12891,8 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionMove(ui::LogicalDisplayId::DEFAULT,
+                                   AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mWindow->consumeDragEvent(true, 150, 50);
     mSecondWindow->consumeDragEvent(false, 50, 50);
 
@@ -11022,7 +12906,7 @@
                                                          .y(50))
                                         .build()))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+    mDragWindow->consumeMotionUp(ui::LogicalDisplayId::DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
     mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
@@ -11035,8 +12919,8 @@
 TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) {
     // Down on second window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionDown());
@@ -11045,7 +12929,7 @@
     // Down on first window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(50))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
                     .build();
@@ -11063,7 +12947,7 @@
     // Trigger cancel
     mDispatcher->cancelCurrentTouch();
     ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionCancel());
-    ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT,
+    ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel(ui::LogicalDisplayId::DEFAULT,
                                                              AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE));
     ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionCancel());
 
@@ -11076,14 +12960,14 @@
 
     // Inject a simple gesture, ensure dispatcher not crashed
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               PointF{50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, PointF{50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
 
     const MotionEvent moveEvent =
             MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
                     .build();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
@@ -11093,7 +12977,7 @@
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionMove());
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ui::LogicalDisplayId::DEFAULT,
                              {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp());
@@ -11103,7 +12987,7 @@
     // Start hovering over the window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE,
-                                ADISPLAY_ID_DEFAULT, {50, 50}));
+                                ui::LogicalDisplayId::DEFAULT, {50, 50}));
 
     ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
     ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
@@ -11112,27 +12996,110 @@
             << "Drag and drop should not work with a hovering pointer";
 }
 
+/**
+ * Two devices, we use the second pointer of Device A to start the drag, during the drag process, if
+ * we perform a click using Device B, the dispatcher should work well.
+ */
+TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouchAndMultiDevice) {
+    const DeviceId deviceA = 1;
+    const DeviceId deviceB = 2;
+    // First down on second window with deviceA.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(deviceA)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .build());
+    mSecondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA),
+                                            WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    // Second down on first window with deviceA
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(deviceA)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceA),
+                                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    mSecondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA),
+                                            WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    // Perform drag and drop from first window.
+    ASSERT_TRUE(startDrag(/*sendDown=*/false));
+
+    // Click first window with device B, we should ensure dispatcher work well.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(deviceB)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(deviceB),
+                                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_MOUSE)
+                                      .deviceId(deviceB)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+                                      .build());
+    mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(deviceB),
+                                      WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    // Move with device A.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(deviceA)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(51).y(51))
+                                      .build());
+
+    mDragWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    mWindow->consumeDragEvent(false, 51, 51);
+    mSecondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA),
+                                            WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    // Releasing the drag pointer should cause drop.
+    mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(deviceA)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .pointer(PointerBuilder(1, ToolType::FINGER).x(51).y(51))
+                                      .build());
+    mDragWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(deviceA),
+                                          WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)));
+    mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken());
+    mSecondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(deviceA),
+                                            WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+
+    // Release all pointers.
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                      .deviceId(deviceA)
+                                      .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(51))
+                                      .build());
+    mSecondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_UP), WithDeviceId(deviceA),
+                                            WithDisplayId(ui::LogicalDisplayId::DEFAULT)));
+    mWindow->assertNoEvents();
+}
+
 class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};
 
 TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInput(true);
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
     setFocusedWindow(window);
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->waitForIdle();
     window->assertNoEvents();
 
@@ -11140,12 +13107,13 @@
     window->setDropInput(false);
     mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 }
 
@@ -11154,16 +13122,17 @@
             std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
     obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInputIfObscured(true);
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -11171,13 +13140,14 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     // With the flag cleared, the window should get input
@@ -11185,12 +13155,14 @@
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     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);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT,
+                              AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
     window->assertNoEvents();
 }
 
@@ -11199,16 +13171,17 @@
             std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> obscuringWindow =
             sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
-                                       ADISPLAY_ID_DEFAULT);
+                                       ui::LogicalDisplayId::DEFAULT);
     obscuringWindow->setFrame(Rect(0, 0, 50, 50));
     obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
     obscuringWindow->setTouchable(false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
-    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                             "Test window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Test window",
+                                       ui::LogicalDisplayId::DEFAULT);
     window->setDropInputIfObscured(true);
     window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
-    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
     window->setFocusable(true);
     mDispatcher->onWindowInfosChanged(
             {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
@@ -11216,25 +13189,27 @@
     window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
 
     // With the flag set, window should not get any input
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
-                                                 ADISPLAY_ID_DEFAULT));
+                                                 ui::LogicalDisplayId::DEFAULT));
     window->assertNoEvents();
 
     // When the window is no longer obscured because it went on top, it should get input
     mDispatcher->onWindowInfosChanged(
             {{*window->getInfo(), *obscuringWindow->getInfo()}, {}, 0, 0});
 
-    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
-    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ui::LogicalDisplayId::DEFAULT));
+    window->consumeKeyUp(ui::LogicalDisplayId::DEFAULT);
 
     mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
-                                                 AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+                                                 AINPUT_SOURCE_TOUCHSCREEN,
+                                                 ui::LogicalDisplayId::DEFAULT));
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 }
 
@@ -11251,18 +13226,19 @@
 
         mApp = std::make_shared<FakeApplicationHandle>();
         mSecondaryApp = std::make_shared<FakeApplicationHandle>();
-        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+        mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow",
+                                             ui::LogicalDisplayId::DEFAULT);
         mWindow->setFocusable(true);
         setFocusedWindow(mWindow);
-        mSecondWindow =
-                sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+        mSecondWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2",
+                                                   ui::LogicalDisplayId::DEFAULT);
         mSecondWindow->setFocusable(true);
         mThirdWindow =
                 sp<FakeWindowHandle>::make(mSecondaryApp, mDispatcher,
                                            "TestWindow3_SecondaryDisplay", SECOND_DISPLAY_ID);
         mThirdWindow->setFocusable(true);
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, mApp);
         mDispatcher->onWindowInfosChanged(
                 {{*mWindow->getInfo(), *mSecondWindow->getInfo(), *mThirdWindow->getInfo()},
                  {},
@@ -11273,7 +13249,8 @@
 
         // Set main display initial touch mode to InputDispatcher::kDefaultInTouchMode.
         if (mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, WINDOW_PID,
-                                        WINDOW_UID, /*hasPermission=*/true, ADISPLAY_ID_DEFAULT)) {
+                                        WINDOW_UID, /*hasPermission=*/true,
+                                        ui::LogicalDisplayId::DEFAULT)) {
             mWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mSecondWindow->consumeTouchModeEvent(InputDispatcher::kDefaultInTouchMode);
             mThirdWindow->assertNoEvents();
@@ -11292,7 +13269,7 @@
     void changeAndVerifyTouchModeInMainDisplayOnly(bool inTouchMode, gui::Pid pid, gui::Uid uid,
                                                    bool hasPermission) {
         ASSERT_TRUE(mDispatcher->setInTouchMode(inTouchMode, pid, uid, hasPermission,
-                                                ADISPLAY_ID_DEFAULT));
+                                                ui::LogicalDisplayId::DEFAULT));
         mWindow->consumeTouchModeEvent(inTouchMode);
         mSecondWindow->consumeTouchModeEvent(inTouchMode);
         mThirdWindow->assertNoEvents();
@@ -11313,7 +13290,7 @@
     mWindow->setOwnerInfo(gui::Pid::INVALID, gui::Uid::INVALID);
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
                                              ownerUid, /*hasPermission=*/false,
-                                             ADISPLAY_ID_DEFAULT));
+                                             ui::LogicalDisplayId::DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -11331,7 +13308,8 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode,
                                              windowInfo.ownerPid, windowInfo.ownerUid,
-                                             /*hasPermission=*/true, ADISPLAY_ID_DEFAULT));
+                                             /*hasPermission=*/true,
+                                             ui::LogicalDisplayId::DEFAULT));
     mWindow->assertNoEvents();
     mSecondWindow->assertNoEvents();
 }
@@ -11349,9 +13327,9 @@
 TEST_F(InputDispatcherTouchModeChangedTests, CanChangeTouchModeWhenOwningLastInteractedWindow) {
     // Interact with the window first.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
+              injectKeyDown(*mDispatcher, ui::LogicalDisplayId::DEFAULT))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeKeyDown(ui::LogicalDisplayId::DEFAULT);
 
     // Then remove focus.
     mWindow->setFocusable(false);
@@ -11361,7 +13339,8 @@
     const WindowInfo& windowInfo = *mWindow->getInfo();
     ASSERT_TRUE(mDispatcher->setInTouchMode(!InputDispatcher::kDefaultInTouchMode,
                                             windowInfo.ownerPid, windowInfo.ownerUid,
-                                            /*hasPermission=*/false, ADISPLAY_ID_DEFAULT));
+                                            /*hasPermission=*/false,
+                                            ui::LogicalDisplayId::DEFAULT));
 }
 
 class InputDispatcherSpyWindowTest : public InputDispatcherTest {
@@ -11371,8 +13350,9 @@
                 std::make_shared<FakeApplicationHandle>();
         std::string name = "Fake Spy ";
         name += std::to_string(mSpyCount++);
-        sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                              name.c_str(), ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> spy =
+                sp<FakeWindowHandle>::make(application, mDispatcher, name.c_str(),
+                                           ui::LogicalDisplayId::DEFAULT);
         spy->setSpy(true);
         spy->setTrustedOverlay(true);
         return spy;
@@ -11383,7 +13363,7 @@
                 std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setFocusable(true);
         return window;
     }
@@ -11414,9 +13394,10 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -11454,7 +13435,8 @@
     }
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
     std::vector<size_t> eventOrder;
@@ -11492,9 +13474,10 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     spy->assertNoEvents();
 }
 
@@ -11511,20 +13494,22 @@
 
     // Inject an event outside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->assertNoEvents();
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                             ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionUp();
     spy->assertNoEvents();
 
     // Inject an event inside the spy window's touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {5, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {5, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -11545,8 +13530,8 @@
 
     // Inject an event outside the spy window's frame and touchable region.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionOutsideWithZeroedCoords();
@@ -11567,8 +13552,8 @@
             {{*spy->getInfo(), *windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     windowLeft->consumeMotionDown();
     spy->consumeMotionDown();
@@ -11599,8 +13584,8 @@
     mDispatcher->onWindowInfosChanged({{*spyRight->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {50, 50}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {50, 50}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spyRight->assertNoEvents();
@@ -11636,16 +13621,16 @@
 
     // First finger down, no window touched.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     window->assertNoEvents();
 
     // Second finger down on window, the window should receive touch down.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -11655,7 +13640,7 @@
                                 InputEventInjectionSync::WAIT_FOR_RESULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
 
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
     spy->consumeMotionPointerDown(/*pointerIndex=*/1);
 }
 
@@ -11674,11 +13659,11 @@
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyDown(ADISPLAY_ID_NONE);
+    window->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
             << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeKeyUp(ADISPLAY_ID_NONE);
+    window->consumeKeyUp(ui::LogicalDisplayId::INVALID);
 
     spy->assertNoEvents();
 }
@@ -11697,7 +13682,8 @@
             {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy1->consumeMotionDown();
@@ -11712,7 +13698,7 @@
     // The rest of the gesture should only be sent to the second spy window.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT))
+                                ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy2->consumeMotionMove();
     spy1->assertNoEvents();
@@ -11729,19 +13715,21 @@
     mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
-    spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
+    spy->consumeMotionDown(ui::LogicalDisplayId::DEFAULT);
 
     window->releaseChannel();
 
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
 
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+              injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                             ui::LogicalDisplayId::DEFAULT))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
-    spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    spy->consumeMotionUp(ui::LogicalDisplayId::DEFAULT);
 }
 
 /**
@@ -11756,8 +13744,8 @@
 
     // First finger down on the window and the spy.
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {100, 200}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {100, 200}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     spy->consumeMotionDown();
     window->consumeMotionDown();
@@ -11769,7 +13757,7 @@
     // Second finger down on the window and spy, but the window should not receive the pointer down.
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -11784,7 +13772,7 @@
     // Third finger goes down outside all windows, so injection should fail.
     const MotionEvent thirdFingerDownEvent =
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -11812,15 +13800,15 @@
 
     // First finger down on the window only
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {150, 150}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {150, 150}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
 
     // Second finger down on the spy and window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
@@ -11835,7 +13823,7 @@
     // Third finger down on the spy and window
     const MotionEvent thirdFingerDownEvent =
             MotionEventBuilder(POINTER_2_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(150).y(150))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
@@ -11850,8 +13838,14 @@
 
     // Spy window pilfers the pointers.
     EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
-    window->consumeMotionPointerUp(/*idx=*/2, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
-    window->consumeMotionPointerUp(/*idx=*/1, ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_CANCELED);
+    window->consumeMotionPointerUp(/*pointerIdx=*/2,
+                                   AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                         WithFlags(AMOTION_EVENT_FLAG_CANCELED),
+                                         WithPointerCount(3)));
+    window->consumeMotionPointerUp(/*pointerIdx=*/1,
+                                   AllOf(WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                                         WithFlags(AMOTION_EVENT_FLAG_CANCELED),
+                                         WithPointerCount(2)));
 
     spy->assertNoEvents();
     window->assertNoEvents();
@@ -11872,8 +13866,8 @@
 
     // First finger down on both spy and window
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {10, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -11881,7 +13875,7 @@
     // Second finger down on the spy and window
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(50).y(50))
@@ -11915,8 +13909,8 @@
 
     // First finger down on both window and spy
     ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
-              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
-                               {10, 10}))
+              injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+                               ui::LogicalDisplayId::DEFAULT, {10, 10}))
             << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
     window->consumeMotionDown();
     spy->consumeMotionDown();
@@ -11928,7 +13922,7 @@
     // Second finger down on the window only
     const MotionEvent secondFingerDownEvent =
             MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
-                    .displayId(ADISPLAY_ID_DEFAULT)
+                    .displayId(ui::LogicalDisplayId::DEFAULT)
                     .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(10).y(10))
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
@@ -11953,7 +13947,8 @@
  * Pilfer from spy window.
  * Check that the pilfering only affects the pointers that are actually being received by the spy.
  */
-TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     sp<FakeWindowHandle> spy = createSpy();
     spy->setFrame(Rect(0, 0, 200, 200));
     sp<FakeWindowHandle> leftWindow = createForeground();
@@ -12011,6 +14006,83 @@
     rightWindow->assertNoEvents();
 }
 
+/**
+ * A window on the left and a window on the right. Also, a spy window that's above all of the
+ * windows, and spanning both left and right windows.
+ * Send simultaneous motion streams from two different devices, one to the left window, and another
+ * to the right window.
+ * Pilfer from spy window.
+ * Check that the pilfering affects all of the pointers that are actually being received by the spy.
+ * The spy should receive both the touch and the stylus events after pilfer.
+ */
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    sp<FakeWindowHandle> spy = createSpy();
+    spy->setFrame(Rect(0, 0, 200, 200));
+    sp<FakeWindowHandle> leftWindow = createForeground();
+    leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+    sp<FakeWindowHandle> rightWindow = createForeground();
+    rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+    constexpr int32_t stylusDeviceId = 1;
+    constexpr int32_t touchDeviceId = 2;
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+    // Stylus down on left window and spy
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+                                      .build());
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+    // Finger down on right window and spy
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+                    .build());
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+    // Act: pilfer from spy. Spy is currently receiving touch events.
+    EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+    leftWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+    rightWindow->consumeMotionEvent(
+            AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+
+    // Continue movements from both stylus and touch. Touch and stylus will be delivered to spy
+    // Instead of sending the two MOVE events for each input device together, and then receiving
+    // them both, process them one at at time. InputConsumer is always in the batching mode, which
+    // means that the two MOVE events will be initially put into a batch. Once the events are
+    // batched, the 'consume' call may result in any of the MOVE events to be sent first (depending
+    // on the implementation of InputConsumer), which would mean that the order of the received
+    // events could be different depending on whether there are 1 or 2 events pending in the
+    // InputChannel at the time the test calls 'consume'. To make assertions simpler here, and to
+    // avoid this confusing behaviour, send and receive each MOVE event separately.
+    mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                      .deviceId(stylusDeviceId)
+                                      .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52))
+                                      .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .deviceId(touchDeviceId)
+                    .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
+                    .build());
+    spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+    spy->assertNoEvents();
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) {
     auto window = createForeground();
     auto spy = createSpy();
@@ -12035,9 +14107,9 @@
     std::pair<sp<FakeWindowHandle>, sp<FakeWindowHandle>> setupStylusOverlayScenario() {
         std::shared_ptr<FakeApplicationHandle> overlayApplication =
                 std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> overlay =
-                sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
-                                           "Stylus interceptor window", ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
+                                                                  "Stylus interceptor window",
+                                                                  ui::LogicalDisplayId::DEFAULT);
         overlay->setFocusable(false);
         overlay->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
         overlay->setTouchable(false);
@@ -12048,11 +14120,11 @@
                 std::make_shared<FakeApplicationHandle>();
         sp<FakeWindowHandle> window =
                 sp<FakeWindowHandle>::make(application, mDispatcher, "Application window",
-                                           ADISPLAY_ID_DEFAULT);
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setFocusable(true);
         window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
 
-        mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+        mDispatcher->setFocusedApplication(ui::LogicalDisplayId::DEFAULT, application);
         mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
         setFocusedWindow(window);
         window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
@@ -12062,13 +14134,13 @@
     void sendFingerEvent(int32_t action) {
         mDispatcher->notifyMotion(
                 generateMotionArgs(action, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
-                                   ADISPLAY_ID_DEFAULT, {PointF{20, 20}}));
+                                   ui::LogicalDisplayId::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}});
+                                   ui::LogicalDisplayId::DEFAULT, {PointF{30, 40}});
         motionArgs.pointerProperties[0].toolType = ToolType::STYLUS;
         mDispatcher->notifyMotion(motionArgs);
     }
@@ -12172,7 +14244,7 @@
 
     InputEventInjectionResult injectTargetedMotion(int32_t action) const {
         return injectMotionEvent(*mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
-                                 ADISPLAY_ID_DEFAULT, {100, 200},
+                                 ui::LogicalDisplayId::DEFAULT, {100, 200},
                                  {AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                   AMOTION_EVENT_INVALID_CURSOR_POSITION},
                                  INJECT_EVENT_TIMEOUT, InputEventInjectionSync::WAIT_FOR_RESULT,
@@ -12180,7 +14252,8 @@
     }
 
     InputEventInjectionResult injectTargetedKey(int32_t action) const {
-        return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0, ADISPLAY_ID_NONE,
+        return inputdispatcher::injectKey(*mDispatcher, action, /*repeatCount=*/0,
+                                          ui::LogicalDisplayId::INVALID,
                                           InputEventInjectionSync::WAIT_FOR_RESULT,
                                           INJECT_EVENT_TIMEOUT, /*allowKeyRepeat=*/false, {mUid},
                                           mPolicyFlags);
@@ -12189,8 +14262,9 @@
     sp<FakeWindowHandle> createWindow(const char* name) const {
         std::shared_ptr<FakeApplicationHandle> overlayApplication =
                 std::make_shared<FakeApplicationHandle>();
-        sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
-                                                                 name, ADISPLAY_ID_DEFAULT);
+        sp<FakeWindowHandle> window =
+                sp<FakeWindowHandle>::make(overlayApplication, mDispatcher, name,
+                                           ui::LogicalDisplayId::DEFAULT);
         window->setOwnerInfo(mPid, mUid);
         return window;
     }
@@ -12212,7 +14286,7 @@
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               owner.injectTargetedKey(AKEY_EVENT_ACTION_DOWN));
-    window->consumeKeyDown(ADISPLAY_ID_NONE);
+    window->consumeKeyDown(ui::LogicalDisplayId::INVALID);
 }
 
 TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
@@ -12277,7 +14351,7 @@
     // A user that has injection permission can inject into any window.
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
               injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
-                                ADISPLAY_ID_DEFAULT));
+                                ui::LogicalDisplayId::DEFAULT));
     randosSpy->consumeMotionDown();
     window->consumeMotionDown();
 
@@ -12285,7 +14359,7 @@
     randosSpy->consumeFocusEvent(true);
 
     EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher));
-    randosSpy->consumeKeyDown(ADISPLAY_ID_NONE);
+    randosSpy->consumeKeyDown(ui::LogicalDisplayId::INVALID);
     window->assertNoEvents();
 }
 
@@ -12312,13 +14386,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -12335,11 +14410,14 @@
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Hover move to the right window.
@@ -12352,11 +14430,14 @@
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
 
     // Stop hovering.
@@ -12368,11 +14449,14 @@
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 }
 
@@ -12380,13 +14464,14 @@
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
-    sp<FakeWindowHandle> spy =
-            sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window",
+                                                          ui::LogicalDisplayId::DEFAULT);
     spy->setFrame(Rect(0, 0, 200, 100));
     spy->setTrustedOverlay(true);
     spy->setSpy(true);
@@ -12403,11 +14488,14 @@
     left->consumeMotionDown();
     spy->consumeMotionDown();
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Second pointer down on right window.
@@ -12421,17 +14509,23 @@
     right->consumeMotionDown();
     spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/1));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/1));
 
     // Second pointer up.
@@ -12445,17 +14539,23 @@
     right->consumeMotionUp();
     spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/1));
 
     // First pointer up.
@@ -12467,29 +14567,36 @@
     left->consumeMotionUp();
     spy->consumeMotionUp();
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 }
 
-TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse_legacy) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, false);
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
     sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
-                                                           ADISPLAY_ID_DEFAULT);
+                                                           ui::LogicalDisplayId::DEFAULT);
     left->setFrame(Rect(0, 0, 100, 100));
-    sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
-                                                            "Right Window", ADISPLAY_ID_DEFAULT);
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
     right->setFrame(Rect(100, 0, 200, 100));
 
     mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
 
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
                                                 /*pointerId=*/0));
 
     // Hover move into the window.
@@ -12503,7 +14610,8 @@
 
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
 
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
 
     // Move the mouse with another device. This cancels the hovering pointer from the first device.
@@ -12520,9 +14628,10 @@
 
     // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
     // a HOVER_EXIT from the first device.
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
                                                SECOND_DEVICE_ID,
                                                /*pointerId=*/0));
 
@@ -12538,11 +14647,95 @@
 
     left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
     right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
-    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
                                                /*pointerId=*/0));
-    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
                                                 SECOND_DEVICE_ID,
                                                 /*pointerId=*/0));
 }
 
+/**
+ * TODO(b/313689709) - correctly support multiple mouse devices, because they should be controlling
+ * the same cursor, and therefore have a shared motion event stream.
+ */
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+    SCOPED_FLAG_OVERRIDE(enable_multi_device_same_window_stream, true);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+    sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+                                                           ui::LogicalDisplayId::DEFAULT);
+    left->setFrame(Rect(0, 0, 100, 100));
+    sp<FakeWindowHandle> right =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Right Window",
+                                       ui::LogicalDisplayId::DEFAULT);
+    right->setFrame(Rect(100, 0, 200, 100));
+
+    mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
+                                                /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                DEVICE_ID,
+                                                /*pointerId=*/0));
+
+    // Hover move into the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+                    .rawXCursorPosition(50)
+                    .rawYCursorPosition(50)
+                    .deviceId(DEVICE_ID)
+                    .build());
+
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse with another device
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+                    .rawXCursorPosition(51)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+    left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+    // a HOVER_EXIT from the first device.
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               SECOND_DEVICE_ID,
+                                               /*pointerId=*/0));
+
+    // Move the mouse outside the window. Document the current behavior, where the window does not
+    // receive HOVER_EXIT even though the mouse left the window.
+    mDispatcher->notifyMotion(
+            MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+                    .rawXCursorPosition(150)
+                    .rawYCursorPosition(50)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .build());
+
+    right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                               DEVICE_ID,
+                                               /*pointerId=*/0));
+    ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ui::LogicalDisplayId::DEFAULT,
+                                                SECOND_DEVICE_ID,
+                                                /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherTest, FocusedDisplayChangeIsNotified) {
+    mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+    mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputMapperTest.cpp b/services/inputflinger/tests/InputMapperTest.cpp
index 2aecab9..7dff144 100644
--- a/services/inputflinger/tests/InputMapperTest.cpp
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -26,17 +26,13 @@
 namespace android {
 
 using testing::_;
+using testing::NiceMock;
 using testing::Return;
+using testing::ReturnRef;
 
 void InputMapperUnitTest::SetUpWithBus(int bus) {
-    mFakePointerController = std::make_shared<FakePointerController>();
-    mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
-    mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
     mFakePolicy = sp<FakeInputReaderPolicy>::make();
 
-    EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
-            .WillRepeatedly(Return(mFakePointerController));
-
     EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
 
     EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
@@ -49,30 +45,24 @@
     EXPECT_CALL(mMockEventHub, getConfiguration(EVENTHUB_ID)).WillRepeatedly([&](int32_t) {
         return mPropertyMap;
     });
-}
 
-void InputMapperUnitTest::createDevice() {
-    mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
-                                            /*generation=*/2, mIdentifier);
-    mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
+    mDevice = std::make_unique<NiceMock<MockInputDevice>>(&mMockInputReaderContext, DEVICE_ID,
+                                                          /*generation=*/2, mIdentifier);
+    ON_CALL((*mDevice), getConfiguration).WillByDefault(ReturnRef(mPropertyMap));
     mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
-    std::list<NotifyArgs> args =
-            mDevice->configure(systemTime(), mReaderConfiguration, /*changes=*/{});
-    ASSERT_THAT(args, testing::ElementsAre(testing::VariantWith<NotifyDeviceResetArgs>(_)));
 }
 
 void InputMapperUnitTest::setupAxis(int axis, bool valid, int32_t min, int32_t max,
                                     int32_t resolution) {
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, _))
-            .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
-                outAxisInfo->valid = valid;
-                outAxisInfo->minValue = min;
-                outAxisInfo->maxValue = max;
-                outAxisInfo->flat = 0;
-                outAxisInfo->fuzz = 0;
-                outAxisInfo->resolution = resolution;
-                return valid ? OK : -1;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis))
+            .WillRepeatedly(Return(valid ? std::optional<RawAbsoluteAxisInfo>{{
+                                                   .minValue = min,
+                                                   .maxValue = max,
+                                                   .flat = 0,
+                                                   .fuzz = 0,
+                                                   .resolution = resolution,
+                                           }}
+                                         : std::nullopt));
 }
 
 void InputMapperUnitTest::expectScanCodes(bool present, std::set<int> scanCodes) {
@@ -96,6 +86,13 @@
     }
 }
 
+void InputMapperUnitTest::setSwitchState(int32_t state, std::set<int32_t> switchCodes) {
+    for (const auto& switchCode : switchCodes) {
+        EXPECT_CALL(mMockEventHub, getSwitchState(EVENTHUB_ID, switchCode))
+                .WillRepeatedly(testing::Return(static_cast<int>(state)));
+    }
+}
+
 std::list<NotifyArgs> InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) {
     nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
     return process(when, type, code, value);
@@ -110,7 +107,7 @@
     event.type = type;
     event.code = code;
     event.value = value;
-    return mMapper->process(&event);
+    return mMapper->process(event);
 }
 
 const char* InputMapperTest::DEVICE_NAME = "device";
@@ -178,8 +175,8 @@
     return device;
 }
 
-void InputMapperTest::setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
-                                                   ui::Rotation orientation,
+void InputMapperTest::setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width,
+                                                   int32_t height, ui::Rotation orientation,
                                                    const std::string& uniqueId,
                                                    std::optional<uint8_t> physicalPort,
                                                    ViewportType viewportType) {
@@ -201,7 +198,7 @@
     event.type = type;
     event.code = code;
     event.value = value;
-    std::list<NotifyArgs> processArgList = mapper.process(&event);
+    std::list<NotifyArgs> processArgList = mapper.process(event);
     for (const NotifyArgs& args : processArgList) {
         mFakeListener->notify(args);
     }
diff --git a/services/inputflinger/tests/InputMapperTest.h b/services/inputflinger/tests/InputMapperTest.h
index e176a65..fc27e4f 100644
--- a/services/inputflinger/tests/InputMapperTest.h
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -40,18 +40,9 @@
 protected:
     static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
-    static constexpr float INITIAL_CURSOR_X = 400;
-    static constexpr float INITIAL_CURSOR_Y = 240;
     virtual void SetUp() override { SetUpWithBus(0); }
     virtual void SetUpWithBus(int bus);
 
-    /**
-     * Initializes mDevice and mDeviceContext. When this happens, mDevice takes a copy of
-     * mPropertyMap, so tests that need to set configuration properties should do so before calling
-     * this. Others will most likely want to call it in their SetUp method.
-     */
-    void createDevice();
-
     void setupAxis(int axis, bool valid, int32_t min, int32_t max, int32_t resolution);
 
     void expectScanCodes(bool present, std::set<int> scanCodes);
@@ -60,15 +51,16 @@
 
     void setKeyCodeState(KeyState state, std::set<int> keyCodes);
 
+    void setSwitchState(int32_t state, std::set<int32_t> switchCodes);
+
     std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
     std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
 
     InputDeviceIdentifier mIdentifier;
     MockEventHubInterface mMockEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
-    std::shared_ptr<FakePointerController> mFakePointerController;
     MockInputReaderContext mMockInputReaderContext;
-    std::unique_ptr<InputDevice> mDevice;
+    std::unique_ptr<MockInputDevice> mDevice;
 
     std::unique_ptr<InputDeviceContext> mDeviceContext;
     InputReaderConfiguration mReaderConfiguration;
@@ -124,14 +116,15 @@
     T& constructAndAddMapper(Args... args) {
         // ensure a device entry exists for this eventHubId
         mDevice->addEmptyEventHubDevice(EVENTHUB_ID);
-        // configure the empty device
-        configureDevice(/*changes=*/{});
 
-        return mDevice->constructAndAddMapper<T>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
-                                                 args...);
+        auto& mapper =
+                mDevice->constructAndAddMapper<T>(EVENTHUB_ID,
+                                                  mFakePolicy->getReaderConfiguration(), args...);
+        configureDevice(/*changes=*/{});
+        return mapper;
     }
 
-    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+    void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType);
diff --git a/services/inputflinger/tests/InputProcessorConverter_test.cpp b/services/inputflinger/tests/InputProcessorConverter_test.cpp
index 4b42f4b..bdf156c 100644
--- a/services/inputflinger/tests/InputProcessorConverter_test.cpp
+++ b/services/inputflinger/tests/InputProcessorConverter_test.cpp
@@ -17,7 +17,6 @@
 #include "../InputCommonConverter.h"
 
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <utils/BitSet.h>
 
 using namespace aidl::android::hardware::input;
@@ -39,7 +38,7 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.5);
     static constexpr nsecs_t downTime = 2;
     NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
-                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT,
                                 /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
                                 /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
diff --git a/services/inputflinger/tests/InputProcessor_test.cpp b/services/inputflinger/tests/InputProcessor_test.cpp
index 3b7cbfa..d4c5a00 100644
--- a/services/inputflinger/tests/InputProcessor_test.cpp
+++ b/services/inputflinger/tests/InputProcessor_test.cpp
@@ -16,7 +16,6 @@
 
 #include "../InputProcessor.h"
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 
 #include "TestInputListener.h"
 
@@ -45,7 +44,7 @@
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1);
     static constexpr nsecs_t downTime = 2;
     NotifyMotionArgs motionArgs(/*sequenceNum=*/1, /*eventTime=*/downTime, /*readTime=*/2,
-                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT,
+                                /*deviceId=*/3, AINPUT_SOURCE_ANY, ui::LogicalDisplayId::DEFAULT,
                                 /*policyFlags=*/4, AMOTION_EVENT_ACTION_DOWN, /*actionButton=*/0,
                                 /*flags=*/0, AMETA_NONE, /*buttonState=*/0,
                                 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE,
@@ -64,24 +63,10 @@
     void SetUp() override { mProcessor = std::make_unique<InputProcessor>(mTestListener); }
 };
 
-/**
- * Create a basic configuration change and send it to input processor.
- * Expect that the event is received by the next input stage, unmodified.
- */
-TEST_F(InputProcessorTest, SendToNextStage_NotifyConfigurationChangedArgs) {
-    // Create a basic configuration change and send to processor
-    NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
-
-    mProcessor->notifyConfigurationChanged(args);
-    NotifyConfigurationChangedArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
-    ASSERT_EQ(args, outArgs);
-}
-
 TEST_F(InputProcessorTest, SendToNextStage_NotifyKeyArgs) {
     // Create a basic key event and send to processor
     NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0,
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 835f8b8..17c37d5 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -24,29 +24,25 @@
 #include <InputReader.h>
 #include <InputReaderBase.h>
 #include <InputReaderFactory.h>
-#include <JoystickInputMapper.h>
 #include <KeyboardInputMapper.h>
 #include <MultiTouchInputMapper.h>
+#include <NotifyArgsBuilders.h>
 #include <PeripheralController.h>
 #include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
-#include <SwitchInputMapper.h>
 #include <TestEventMatchers.h>
 #include <TestInputListener.h>
 #include <TouchInputMapper.h>
 #include <UinputDevice.h>
-#include <VibratorInputMapper.h>
 #include <android-base/thread_annotations.h>
 #include <com_android_input_flags.h>
 #include <ftl/enum.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <ui/Rotation.h>
 
 #include <thread>
 #include "FakeEventHub.h"
 #include "FakeInputReaderPolicy.h"
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InstrumentedInputReader.h"
 #include "TestConstants.h"
@@ -57,17 +53,18 @@
 
 using namespace ftl::flag_operators;
 using testing::AllOf;
+using testing::VariantWith;
 using std::chrono_literals::operator""ms;
 using std::chrono_literals::operator""s;
 
 // Arbitrary display properties.
-static constexpr int32_t DISPLAY_ID = 0;
+static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 static const std::string DISPLAY_UNIQUE_ID = "local:1";
-static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
-static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2";
+static constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID =
+        ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
 static constexpr int32_t DISPLAY_WIDTH = 480;
 static constexpr int32_t DISPLAY_HEIGHT = 800;
-static constexpr int32_t VIRTUAL_DISPLAY_ID = 1;
+static constexpr ui::LogicalDisplayId VIRTUAL_DISPLAY_ID = ui::LogicalDisplayId{1};
 static constexpr int32_t VIRTUAL_DISPLAY_WIDTH = 400;
 static constexpr int32_t VIRTUAL_DISPLAY_HEIGHT = 500;
 static const char* VIRTUAL_DISPLAY_UNIQUE_ID = "virtual:1";
@@ -310,9 +307,9 @@
         return {};
     }
 
-    std::list<NotifyArgs> process(const RawEvent* rawEvent) override {
+    std::list<NotifyArgs> process(const RawEvent& rawEvent) override {
         std::scoped_lock<std::mutex> lock(mLock);
-        mLastEvent = *rawEvent;
+        mLastEvent = rawEvent;
         mProcessWasCalled = true;
         mStateChangedCondition.notify_all();
         return mProcessResult;
@@ -359,7 +356,7 @@
     virtual void fadePointer() {
     }
 
-    virtual std::optional<int32_t> getAssociatedDisplay() {
+    virtual std::optional<ui::LogicalDisplayId> getAssociatedDisplay() {
         if (mViewport) {
             return std::make_optional(mViewport->displayId);
         }
@@ -418,8 +415,8 @@
     const std::string externalUniqueId = "local:1";
     const std::string virtualUniqueId1 = "virtual:2";
     const std::string virtualUniqueId2 = "virtual:3";
-    constexpr int32_t virtualDisplayId1 = 2;
-    constexpr int32_t virtualDisplayId2 = 3;
+    constexpr ui::LogicalDisplayId virtualDisplayId1 = ui::LogicalDisplayId{2};
+    constexpr ui::LogicalDisplayId virtualDisplayId2 = ui::LogicalDisplayId{3};
 
     // Add an internal viewport
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -476,8 +473,8 @@
 TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) {
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t displayId1 = 2;
-    constexpr int32_t displayId2 = 3;
+    constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{2};
+    constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{3};
 
     std::vector<ViewportType> types = {ViewportType::INTERNAL, ViewportType::EXTERNAL,
                                        ViewportType::VIRTUAL};
@@ -521,13 +518,13 @@
 TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t nonDefaultDisplayId = 2;
-    static_assert(nonDefaultDisplayId != ADISPLAY_ID_DEFAULT,
-                  "Test display ID should not be ADISPLAY_ID_DEFAULT");
+    constexpr ui::LogicalDisplayId nonDefaultDisplayId = ui::LogicalDisplayId{2};
+    ASSERT_NE(nonDefaultDisplayId, ui::LogicalDisplayId::DEFAULT)
+            << "Test display ID should not be ui::LogicalDisplayId::DEFAULT ";
 
     // Add the default display first and ensure it gets returned.
     mFakePolicy->clearViewports();
-    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
@@ -537,7 +534,7 @@
     std::optional<DisplayViewport> viewport =
             mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     ASSERT_TRUE(viewport);
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
     ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
 
     // Add the default display second to make sure order doesn't matter.
@@ -545,13 +542,13 @@
     mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
                                     ViewportType::INTERNAL);
-    mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+    mFakePolicy->addDisplayViewport(ui::LogicalDisplayId::DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                     ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
                                     ViewportType::INTERNAL);
 
     viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
     ASSERT_TRUE(viewport);
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, viewport->displayId);
     ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
 }
 
@@ -562,8 +559,8 @@
     constexpr ViewportType type = ViewportType::EXTERNAL;
     const std::string uniqueId1 = "uniqueId1";
     const std::string uniqueId2 = "uniqueId2";
-    constexpr int32_t displayId1 = 1;
-    constexpr int32_t displayId2 = 2;
+    constexpr ui::LogicalDisplayId displayId1 = ui::LogicalDisplayId{1};
+    constexpr ui::LogicalDisplayId displayId2 = ui::LogicalDisplayId{2};
     const uint8_t hdmi1 = 0;
     const uint8_t hdmi2 = 1;
     const uint8_t hdmi3 = 2;
@@ -624,7 +621,6 @@
         if (configuration) {
             mFakeEventHub->addConfigurationMap(eventHubId, configuration);
         }
-        mFakeEventHub->finishDeviceScan();
         mReader->loopOnce();
         mReader->loopOnce();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
@@ -758,8 +754,6 @@
     mReader->pushNextDevice(device);
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
 
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr));
-
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
     ASSERT_EQ(deviceId, resetArgs.deviceId);
@@ -775,7 +769,6 @@
     disableDevice(deviceId);
     mReader->loopOnce();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasNotCalled());
     ASSERT_EQ(device->isEnabled(), false);
 
     enableDevice(deviceId);
@@ -960,16 +953,6 @@
     ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
 }
 
-TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
-    constexpr int32_t eventHubId = 1;
-    addDevice(eventHubId, "ignored", InputDeviceClass::KEYBOARD, nullptr);
-
-    NotifyConfigurationChangedArgs args;
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args));
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-}
-
 TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
@@ -1074,7 +1057,6 @@
     // The device is added after the input port associations are processed since
     // we do not yet support dynamic device-to-display associations.
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
     ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
 
@@ -1104,8 +1086,6 @@
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr));
 
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr));
-
     NotifyDeviceResetArgs resetArgs;
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
     ASSERT_EQ(deviceId, resetArgs.deviceId);
@@ -1165,18 +1145,18 @@
 TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
     NotifyPointerCaptureChangedArgs args;
 
-    auto request = mFakePolicy->setPointerCapture(true);
+    auto request = mFakePolicy->setPointerCapture(/*window=*/sp<BBinder>::make());
     mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
+    ASSERT_TRUE(args.request.isEnable()) << "Pointer Capture should be enabled.";
     ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
 
-    mFakePolicy->setPointerCapture(false);
+    mFakePolicy->setPointerCapture(/*window=*/nullptr);
     mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
     mReader->loopOnce();
     mFakeListener->assertNotifyCaptureWasCalled(&args);
-    ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
+    ASSERT_FALSE(args.request.isEnable()) << "Pointer Capture should be disabled.";
 
     // Verify that the Pointer Capture state is not updated when the configuration value
     // does not change.
@@ -1185,6 +1165,82 @@
     mFakeListener->assertNotifyCaptureWasNotCalled();
 }
 
+TEST_F(InputReaderTest, GetLastUsedInputDeviceId) {
+    constexpr int32_t FIRST_DEVICE_ID = END_RESERVED_ID + 1000;
+    constexpr int32_t SECOND_DEVICE_ID = FIRST_DEVICE_ID + 1;
+    FakeInputMapper& firstMapper =
+            addDeviceWithFakeInputMapper(FIRST_DEVICE_ID, FIRST_DEVICE_ID, "first",
+                                         InputDeviceClass::KEYBOARD, AINPUT_SOURCE_KEYBOARD,
+                                         /*configuration=*/nullptr);
+    FakeInputMapper& secondMapper =
+            addDeviceWithFakeInputMapper(SECOND_DEVICE_ID, SECOND_DEVICE_ID, "second",
+                                         InputDeviceClass::TOUCH_MT, AINPUT_SOURCE_STYLUS,
+                                         /*configuration=*/nullptr);
+
+    ASSERT_EQ(ReservedInputDeviceId::INVALID_INPUT_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Start a new key gesture from the first device
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(firstMapper.getDeviceId(), mReader->getLastUsedInputDeviceId());
+
+    // Start a new touch gesture from the second device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Releasing the key is not a new gesture, so it does not update the last used device
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // But pressing a new key does start a new gesture
+    firstMapper.setProcessResult({KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                          .deviceId(FIRST_DEVICE_ID)
+                                          .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, FIRST_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Moving or ending a touch gesture does not update the last used device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+    secondMapper.setProcessResult({MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                           .deviceId(SECOND_DEVICE_ID)
+                                           .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                                           .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(FIRST_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+
+    // Starting a new hover gesture updates the last used device
+    secondMapper.setProcessResult(
+            {MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+                     .deviceId(SECOND_DEVICE_ID)
+                     .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+                     .build()});
+    mFakeEventHub->enqueueEvent(ARBITRARY_TIME, ARBITRARY_TIME, SECOND_DEVICE_ID, 0, 0, 0);
+    mReader->loopOnce();
+    ASSERT_EQ(SECOND_DEVICE_ID, mReader->getLastUsedInputDeviceId());
+}
+
 class FakeVibratorInputMapper : public FakeInputMapper {
 public:
     FakeVibratorInputMapper(InputDeviceContext& deviceContext,
@@ -1349,8 +1405,6 @@
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::unique_ptr<InputReaderInterface> mReader;
 
-    std::shared_ptr<FakePointerController> mFakePointerController;
-
     constexpr static auto EVENT_HAPPENED_TIMEOUT = 2000ms;
     constexpr static auto EVENT_DID_NOT_HAPPEN_TIMEOUT = 30ms;
 
@@ -1359,8 +1413,6 @@
         GTEST_SKIP();
 #endif
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         setupInputReader();
     }
@@ -1405,9 +1457,8 @@
         // Since this test is run on a real device, all the input devices connected
         // 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());
+        ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
     }
 };
 
@@ -1427,12 +1478,10 @@
     // consider it as a valid device.
     std::unique_ptr<UinputDevice> invalidDevice = createUinputDevice<InvalidUinputDevice>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled());
     ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size());
 
     invalidDevice.reset();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled());
     ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size());
 }
 
@@ -1441,7 +1490,6 @@
 
     std::unique_ptr<UinputHomeKey> keyboard = createUinputDevice<UinputHomeKey>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size());
 
     const auto device = waitForDevice(keyboard->getName());
@@ -1452,7 +1500,6 @@
 
     keyboard.reset();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_EQ(initialNumDevices, mFakePolicy->getInputDevices().size());
 }
 
@@ -1460,21 +1507,14 @@
     std::unique_ptr<UinputHomeKey> keyboard = createUinputDevice<UinputHomeKey>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
 
-    NotifyConfigurationChangedArgs configChangedArgs;
-    ASSERT_NO_FATAL_FAILURE(
-            mTestListener->assertNotifyConfigurationChangedWasCalled(&configChangedArgs));
-    int32_t prevId = configChangedArgs.id;
-    nsecs_t prevTimestamp = configChangedArgs.eventTime;
-
     NotifyKeyArgs keyArgs;
     keyboard->pressAndReleaseHomeKey();
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
-    ASSERT_NE(prevId, keyArgs.id);
-    prevId = keyArgs.id;
-    ASSERT_LE(prevTimestamp, keyArgs.eventTime);
     ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
-    prevTimestamp = keyArgs.eventTime;
+
+    int32_t prevId = keyArgs.id;
+    nsecs_t prevTimestamp = keyArgs.eventTime;
 
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs));
     ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
@@ -1597,13 +1637,12 @@
 
         mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
         const auto info = waitForDevice(mDevice->getName());
         ASSERT_TRUE(info);
         mDeviceInfo = *info;
     }
 
-    void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
+    void setDisplayInfoAndReconfigure(ui::LogicalDisplayId displayId, int32_t width, int32_t height,
                                       ui::Rotation orientation, const std::string& uniqueId,
                                       std::optional<uint8_t> physicalPort,
                                       ViewportType viewportType) {
@@ -1654,8 +1693,6 @@
         } else {
             mFakePolicy->addInputUniqueIdAssociation(INPUT_PORT, UNIQUE_ID);
         }
-        mFakePointerController = std::make_shared<FakePointerController>();
-        mFakePolicy->setPointerController(mFakePointerController);
 
         InputReaderIntegrationTest::setupInputReader();
 
@@ -1668,7 +1705,6 @@
                                      UNIQUE_ID, isInputPortAssociation ? DISPLAY_PORT : NO_PORT,
                                      ViewportType::INTERNAL);
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
         const auto info = waitForDevice(mDevice->getName());
         ASSERT_TRUE(info);
         mDeviceInfo = *info;
@@ -2001,7 +2037,6 @@
     // Connecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
     auto externalStylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     const auto stylusInfo = waitForDevice(externalStylus->getName());
     ASSERT_TRUE(stylusInfo);
 
@@ -2014,7 +2049,6 @@
     // Disconnecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
     externalStylus.reset();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
 
     // Up
@@ -2072,7 +2106,6 @@
         mStylusDeviceLifecycleTracker = createUinputDevice<T>();
         mStylus = mStylusDeviceLifecycleTracker.get();
         ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-        ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
         const auto info = waitForDevice(mStylus->getName());
         ASSERT_TRUE(info);
         mStylusInfo = *info;
@@ -2342,7 +2375,6 @@
     std::unique_ptr<UinputExternalStylusWithPressure> stylus =
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
@@ -2360,7 +2392,6 @@
     std::unique_ptr<UinputExternalStylusWithPressure> stylus =
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
@@ -2406,7 +2437,6 @@
     std::unique_ptr<UinputExternalStylusWithPressure> stylus =
             createUinputDevice<UinputExternalStylusWithPressure>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
@@ -2486,7 +2516,6 @@
     // touch pointers.
     std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
     ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
-    ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
     const auto stylusInfo = waitForDevice(stylus->getName());
     ASSERT_TRUE(stylusInfo);
 
@@ -2907,7 +2936,7 @@
     const auto initialGeneration = mDevice->getGeneration();
     unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                  InputReaderConfiguration::Change::DISPLAY_INFO);
-    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
+    ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueIdByPort());
     ASSERT_GT(mDevice->getGeneration(), initialGeneration);
     ASSERT_EQ(mDevice->getDeviceInfo().getAssociatedDisplayId(), SECONDARY_DISPLAY_ID);
 }
@@ -2988,106 +3017,6 @@
     mapper.assertProcessWasCalled();
 }
 
-// --- SwitchInputMapperTest ---
-
-class SwitchInputMapperTest : public InputMapperTest {
-protected:
-};
-
-TEST_F(SwitchInputMapperTest, GetSources) {
-    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
-
-    ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources());
-}
-
-TEST_F(SwitchInputMapperTest, GetSwitchState) {
-    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
-
-    mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1);
-    ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
-
-    mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 0);
-    ASSERT_EQ(0, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
-}
-
-TEST_F(SwitchInputMapperTest, Process) {
-    SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
-    std::list<NotifyArgs> out;
-    out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_LID, 1);
-    ASSERT_TRUE(out.empty());
-    out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1);
-    ASSERT_TRUE(out.empty());
-    out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SW, SW_HEADPHONE_INSERT, 0);
-    ASSERT_TRUE(out.empty());
-    out = process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-
-    ASSERT_EQ(1u, out.size());
-    const NotifySwitchArgs& args = std::get<NotifySwitchArgs>(*out.begin());
-    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
-    ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT), args.switchValues);
-    ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT),
-            args.switchMask);
-    ASSERT_EQ(uint32_t(0), args.policyFlags);
-}
-
-// --- VibratorInputMapperTest ---
-class VibratorInputMapperTest : public InputMapperTest {
-protected:
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::VIBRATOR); }
-};
-
-TEST_F(VibratorInputMapperTest, GetSources) {
-    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
-}
-
-TEST_F(VibratorInputMapperTest, GetVibratorIds) {
-    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
-
-    ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
-}
-
-TEST_F(VibratorInputMapperTest, Vibrate) {
-    constexpr uint8_t DEFAULT_AMPLITUDE = 192;
-    constexpr int32_t VIBRATION_TOKEN = 100;
-    VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
-
-    VibrationElement pattern(2);
-    VibrationSequence sequence(2);
-    pattern.duration = std::chrono::milliseconds(200);
-    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2},
-                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
-    sequence.addElement(pattern);
-    pattern.duration = std::chrono::milliseconds(500);
-    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4},
-                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
-    sequence.addElement(pattern);
-
-    std::vector<int64_t> timings = {0, 1};
-    std::vector<uint8_t> amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2};
-
-    ASSERT_FALSE(mapper.isVibrating());
-    // Start vibrating
-    std::list<NotifyArgs> out = mapper.vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN);
-    ASSERT_TRUE(mapper.isVibrating());
-    // Verify vibrator state listener was notified.
-    mReader->loopOnce();
-    ASSERT_EQ(1u, out.size());
-    const NotifyVibratorStateArgs& vibrateArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
-    ASSERT_EQ(DEVICE_ID, vibrateArgs.deviceId);
-    ASSERT_TRUE(vibrateArgs.isOn);
-    // Stop vibrating
-    out = mapper.cancelVibrate(VIBRATION_TOKEN);
-    ASSERT_FALSE(mapper.isVibrating());
-    // Verify vibrator state listener was notified.
-    mReader->loopOnce();
-    ASSERT_EQ(1u, out.size());
-    const NotifyVibratorStateArgs& cancelArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
-    ASSERT_EQ(DEVICE_ID, cancelArgs.deviceId);
-    ASSERT_FALSE(cancelArgs.isOn);
-}
-
 // --- SensorInputMapperTest ---
 
 class SensorInputMapperTest : public InputMapperTest {
@@ -3245,13 +3174,17 @@
 
 class KeyboardInputMapperTest : public InputMapperTest {
 protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::ALPHAKEY);
+    }
     const std::string UNIQUE_ID = "local:0";
     const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
     void prepareDisplay(ui::Rotation orientation);
 
     void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
                              int32_t originalKeyCode, int32_t rotatedKeyCode,
-                             int32_t displayId = ADISPLAY_ID_NONE);
+                             ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID);
 };
 
 /* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the
@@ -3264,7 +3197,8 @@
 
 void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper,
                                                   int32_t originalScanCode, int32_t originalKeyCode,
-                                                  int32_t rotatedKeyCode, int32_t displayId) {
+                                                  int32_t rotatedKeyCode,
+                                                  ui::LogicalDisplayId displayId) {
     NotifyKeyArgs args;
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, originalScanCode, 1);
@@ -3284,8 +3218,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetSources) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources());
 }
@@ -3300,8 +3233,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3398,12 +3330,11 @@
 TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) {
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_B, 0, AKEYCODE_B, 0);
-    mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
+    mFakeEventHub->setKeyRemapping(EVENTHUB_ID, {{AKEYCODE_A, AKEYCODE_B}});
     // Key down by scan code.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
     NotifyKeyArgs args;
@@ -3423,8 +3354,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Key down
@@ -3446,8 +3376,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3487,8 +3416,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     prepareDisplay(ui::ROTATION_90);
     ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
@@ -3509,8 +3437,7 @@
 
     addConfigurationProperty("keyboard.orientationAware", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     prepareDisplay(ui::ROTATION_0);
     ASSERT_NO_FATAL_FAILURE(
@@ -3581,23 +3508,22 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
-    // Display id should be ADISPLAY_ID_NONE without any display configuration.
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
 
     prepareDisplay(ui::ROTATION_0);
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 1);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_UP, 0);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
-    ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, args.displayId);
 }
 
 TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
@@ -3607,11 +3533,10 @@
 
     addConfigurationProperty("keyboard.orientationAware", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
-    // Display id should be ADISPLAY_ID_NONE without any display configuration.
+    // Display id should be LogicalDisplayId::INVALID without any display configuration.
     // ^--- already checked by the previous test
 
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -3622,7 +3547,7 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
     ASSERT_EQ(DISPLAY_ID, args.displayId);
 
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     clearViewports();
     setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
@@ -3635,8 +3560,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1);
     ASSERT_EQ(1, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
@@ -3647,8 +3571,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
     ASSERT_EQ(AKEYCODE_Z, mapper.getKeyCodeForKeyLocation(AKEYCODE_Y))
@@ -3660,8 +3583,7 @@
 
 TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1);
     ASSERT_EQ(1, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
@@ -3672,8 +3594,7 @@
 
 TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
 
@@ -3692,8 +3613,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3758,8 +3678,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // Meta state should be AMETA_NONE after reset
     std::list<NotifyArgs> unused = mapper.reset(ARBITRARY_TIME);
@@ -3808,16 +3727,14 @@
     mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
     KeyboardInputMapper& mapper2 =
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -3837,7 +3754,7 @@
     ASSERT_FALSE(device2->isEnabled());
 
     // Prepare second display.
-    constexpr int32_t newDisplayId = 2;
+    constexpr ui::LogicalDisplayId newDisplayId = ui::LogicalDisplayId{2};
     setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                  UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
     setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
@@ -3879,8 +3796,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3930,8 +3846,7 @@
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -3950,11 +3865,9 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     // Suppose we have two mappers. (DPAD + KEYBOARD)
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
-                                               AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Initial metastate is AMETA_NONE.
     ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
 
@@ -3972,8 +3885,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
 
     KeyboardInputMapper& mapper1 =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     // keyboard 2.
     const std::string USB2 = "USB2";
@@ -3995,8 +3907,7 @@
             device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
                                                                 mFakePolicy
                                                                         ->getReaderConfiguration(),
-                                                                AINPUT_SOURCE_KEYBOARD,
-                                                                AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+                                                                AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -4052,8 +3963,7 @@
     mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     // Key down by scan code.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -4078,8 +3988,7 @@
 }
 
 TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                               AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     std::list<NotifyArgs> unused =
             mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
                                /*changes=*/{});
@@ -4110,8 +4019,7 @@
                                     RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
 
     // Configuration
-    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                               AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     InputReaderConfiguration config;
     std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
 
@@ -4122,8 +4030,7 @@
 TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) {
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
     NotifyKeyArgs args;
 
     // Key down
@@ -4132,14 +4039,72 @@
     ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
 }
 
-// --- KeyboardInputMapperTest_ExternalDevice ---
+/**
+ * When there is more than one KeyboardInputMapper for an InputDevice, each mapper should produce
+ * events that use the shared keyboard source across all mappers. This is to ensure that each
+ * input device generates key events in a consistent manner, regardless of which mapper produces
+ * the event.
+ */
+TEST_F(KeyboardInputMapperTest, UsesSharedKeyboardSource) {
+    mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
 
-class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
+    // Add a mapper with SOURCE_KEYBOARD
+    KeyboardInputMapper& keyboardMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
+
+    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
+    process(keyboardMapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+    ASSERT_NO_FATAL_FAILURE(
+            mFakeListener->assertNotifyKeyWasCalled(WithSource(AINPUT_SOURCE_KEYBOARD)));
+
+    // Add a mapper with SOURCE_DPAD
+    KeyboardInputMapper& dpadMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD);
+    for (auto* mapper : {&keyboardMapper, &dpadMapper}) {
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD)));
+    }
+
+    // Add a mapper with SOURCE_GAMEPAD
+    KeyboardInputMapper& gamepadMapper =
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_GAMEPAD);
+    for (auto* mapper : {&keyboardMapper, &dpadMapper, &gamepadMapper}) {
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 1);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
+        process(*mapper, ARBITRARY_TIME, 0, EV_KEY, KEY_HOME, 0);
+        ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(
+                WithSource(AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD)));
+    }
+}
+
+// --- KeyboardInputMapperTest_ExternalAlphabeticDevice ---
+
+class KeyboardInputMapperTest_ExternalAlphabeticDevice : public InputMapperTest {
 protected:
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL); }
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::ALPHAKEY | InputDeviceClass::EXTERNAL);
+    }
 };
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_AlphabeticKeyboard) {
+// --- KeyboardInputMapperTest_ExternalNonAlphabeticDevice ---
+
+class KeyboardInputMapperTest_ExternalNonAlphabeticDevice : public InputMapperTest {
+protected:
+    void SetUp() override {
+        InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::KEYBOARD |
+                               InputDeviceClass::EXTERNAL);
+    }
+};
+
+TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, WakeBehavior_AlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should also trigger
     // wake if triggered from external devices.
 
@@ -4149,8 +4114,7 @@
                           POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -4178,7 +4142,7 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior_NoneAlphabeticKeyboard) {
+TEST_F(KeyboardInputMapperTest_ExternalNonAlphabeticDevice, WakeBehavior_NonAlphabeticKeyboard) {
     // For external devices, keys will trigger wake on key down. Media keys should not trigger
     // wake if triggered from external non-alphaebtic keyboard (e.g. headsets).
 
@@ -4187,8 +4151,7 @@
                           POLICY_FLAG_WAKE);
 
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_PLAY, 1);
     NotifyKeyArgs args;
@@ -4208,7 +4171,7 @@
     ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
 }
 
-TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
+TEST_F(KeyboardInputMapperTest_ExternalAlphabeticDevice, DoNotWakeByDefaultBehavior) {
     // Tv Remote key's wake behavior is prescribed by the keylayout file.
 
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
@@ -4217,8 +4180,7 @@
 
     addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
     KeyboardInputMapper& mapper =
-            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
-                                                       AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+            constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
     NotifyKeyArgs args;
@@ -4430,15 +4392,15 @@
     void prepareButtons();
     void prepareAxes(int axes);
 
-    void processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y);
-    void processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y);
-    void processUp(SingleTouchInputMapper& mappery);
-    void processPressure(SingleTouchInputMapper& mapper, int32_t pressure);
-    void processToolMajor(SingleTouchInputMapper& mapper, int32_t toolMajor);
-    void processDistance(SingleTouchInputMapper& mapper, int32_t distance);
-    void processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, int32_t tiltY);
-    void processKey(SingleTouchInputMapper& mapper, int32_t code, int32_t value);
-    void processSync(SingleTouchInputMapper& mapper);
+    std::list<NotifyArgs> processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y);
+    std::list<NotifyArgs> processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y);
+    std::list<NotifyArgs> processUp(SingleTouchInputMapper& mappery);
+    std::list<NotifyArgs> processPressure(SingleTouchInputMapper& mapper, int32_t pressure);
+    std::list<NotifyArgs> processToolMajor(SingleTouchInputMapper& mapper, int32_t toolMajor);
+    std::list<NotifyArgs> processDistance(SingleTouchInputMapper& mapper, int32_t distance);
+    std::list<NotifyArgs> processTilt(SingleTouchInputMapper& mapper, int32_t tiltX, int32_t tiltY);
+    std::list<NotifyArgs> processKey(SingleTouchInputMapper& mapper, int32_t code, int32_t value);
+    std::list<NotifyArgs> processSync(SingleTouchInputMapper& mapper);
 };
 
 void SingleTouchInputMapperTest::prepareButtons() {
@@ -4468,47 +4430,57 @@
     }
 }
 
-void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper& mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processDown(SingleTouchInputMapper& mapper,
+                                                              int32_t x, int32_t y) {
+    std::list<NotifyArgs> args;
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 1);
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
+    return args;
 }
 
-void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper& mapper, int32_t x, int32_t y) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processMove(SingleTouchInputMapper& mapper,
+                                                              int32_t x, int32_t y) {
+    std::list<NotifyArgs> args;
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_X, x);
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_Y, y);
+    return args;
 }
 
-void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 0);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processUp(SingleTouchInputMapper& mapper) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_TOUCH, 0);
 }
 
-void SingleTouchInputMapperTest::processPressure(SingleTouchInputMapper& mapper, int32_t pressure) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_PRESSURE, pressure);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processPressure(SingleTouchInputMapper& mapper,
+                                                                  int32_t pressure) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_PRESSURE, pressure);
 }
 
-void SingleTouchInputMapperTest::processToolMajor(SingleTouchInputMapper& mapper,
-                                                  int32_t toolMajor) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processToolMajor(SingleTouchInputMapper& mapper,
+                                                                   int32_t toolMajor) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
 }
 
-void SingleTouchInputMapperTest::processDistance(SingleTouchInputMapper& mapper, int32_t distance) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_DISTANCE, distance);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processDistance(SingleTouchInputMapper& mapper,
+                                                                  int32_t distance) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_DISTANCE, distance);
 }
 
-void SingleTouchInputMapperTest::processTilt(SingleTouchInputMapper& mapper, int32_t tiltX,
-                                             int32_t tiltY) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_X, tiltX);
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_Y, tiltY);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processTilt(SingleTouchInputMapper& mapper,
+                                                              int32_t tiltX, int32_t tiltY) {
+    std::list<NotifyArgs> args;
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_X, tiltX);
+    args += process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, ABS_TILT_Y, tiltY);
+    return args;
 }
 
-void SingleTouchInputMapperTest::processKey(SingleTouchInputMapper& mapper, int32_t code,
-                                            int32_t value) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processKey(SingleTouchInputMapper& mapper,
+                                                             int32_t code, int32_t value) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, code, value);
 }
 
-void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) {
-    process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+std::list<NotifyArgs> SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) {
+    return process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
 }
 
 TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
@@ -4599,6 +4571,42 @@
     ASSERT_FALSE(flags[1]);
 }
 
+TEST_F(SingleTouchInputMapperTest, DeviceTypeChange_RecalculatesRawToDisplayTransform) {
+    prepareDisplay(ui::ROTATION_0);
+    prepareAxes(POSITION);
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+    const int32_t x = 900;
+    const int32_t y = 75;
+    std::list<NotifyArgs> args;
+    args += processDown(mapper, x, y);
+    args += processSync(mapper);
+
+    // Assert that motion event is received in display coordinate space for deviceType touchScreen.
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                              WithCoords(toDisplayX(x), toDisplayY(y))))));
+
+    // Add device type association after the device was created.
+    mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
+    // Send update to the mapper.
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
+
+    args.clear();
+    args += processDown(mapper, x, y);
+    args += processSync(mapper);
+
+    // Assert that motion event is received in raw coordinate space for deviceType touchNavigation.
+    ASSERT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                              WithCoords(x - RAW_X_MIN, y - RAW_Y_MIN)))));
+}
+
 TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(ui::ROTATION_0);
@@ -5344,10 +5352,6 @@
 }
 
 TEST_F(SingleTouchInputMapperTest, Process_DoesntCheckPhysicalFrameForTouchpads) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-
     addConfigurationProperty("touch.deviceType", "pointer");
     prepareAxes(POSITION);
     prepareDisplay(ui::ROTATION_0);
@@ -5419,6 +5423,9 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             x, y, pressure, size, tool, tool, tool, tool, orientation, distance));
     ASSERT_EQ(tilt, args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TILT));
+    ASSERT_EQ(args.flags,
+              AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+                      AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION);
 }
 
 TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
@@ -6189,53 +6196,7 @@
     SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
-}
-
-TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareButtons();
-    prepareAxes(POSITION);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(true);
-    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
-
-    processKey(mapper, BTN_TOOL_PEN, 1);
-    processMove(mapper, 100, 200);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_TRUE(fakePointerController->isPointerShown());
-    ASSERT_NO_FATAL_FAILURE(
-            fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
-}
-
-TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareButtons();
-    prepareAxes(POSITION);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(false);
-    SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
-
-    processKey(mapper, BTN_TOOL_PEN, 1);
-    processMove(mapper, 100, 200);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_FALSE(fakePointerController->isPointerShown());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
@@ -6258,7 +6219,7 @@
                                InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
 
     // Check whether device type update was successful.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
+    ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION | AINPUT_SOURCE_TOUCHPAD, mDevice->getSources());
 }
 
 TEST_F(SingleTouchInputMapperTest, HoverEventsOutsidePhysicalFrameAreIgnored) {
@@ -6714,15 +6675,27 @@
 
 class ExternalStylusFusionTest : public SingleTouchInputMapperTest {
 public:
-    SingleTouchInputMapper& initializeInputMapperWithExternalStylus() {
+    void SetUp() override {
+        SingleTouchInputMapperTest::SetUp();
+        mExternalStylusDeviceInfo = {};
+        mStylusState = {};
+    }
+
+    SingleTouchInputMapper& initializeInputMapperWithExternalStylus(bool supportsPressure = true) {
         addConfigurationProperty("touch.deviceType", "touchScreen");
         prepareDisplay(ui::ROTATION_0);
         prepareButtons();
         prepareAxes(POSITION);
         auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
 
+        if (supportsPressure) {
+            mExternalStylusDeviceInfo.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE,
+                                                     AINPUT_SOURCE_STYLUS, 0.0f, 1.0f, 0.0f, 0.0f,
+                                                     0.0f);
+            mStylusState.pressure = 0.f;
+        }
+
         mStylusState.when = ARBITRARY_TIME;
-        mStylusState.pressure = 0.f;
         mStylusState.toolType = ToolType::STYLUS;
         mReader->getContext()->setExternalStylusDevices({mExternalStylusDeviceInfo});
         configureDevice(InputReaderConfiguration::Change::EXTERNAL_STYLUS_PRESENCE);
@@ -6830,11 +6803,17 @@
     InputDeviceInfo mExternalStylusDeviceInfo{};
 };
 
-TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
+TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSourceWithPressure) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
     ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources());
 }
 
+TEST_F(ExternalStylusFusionTest, DoesNotUseBluetoothStylusSourceWithoutPressure) {
+    SingleTouchInputMapper& mapper =
+            initializeInputMapperWithExternalStylus(/*supportsPressure=*/false);
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
+}
+
 TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
     SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
     ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
@@ -7907,6 +7886,7 @@
     ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
             x, y, pressure, size, touchMajor, touchMinor, toolMajor, toolMinor,
             orientation, distance));
+    ASSERT_EQ(args.flags, AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION);
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_GeometricCalibration) {
@@ -8717,21 +8697,12 @@
 }
 
 TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
-    // Setup for second display.
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(100, 200);
-    mFakePolicy->setPointerController(fakePointerController);
-
-    mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
 
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
-    // Check source is mouse that would obtain the PointerController.
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
 
     NotifyMotionArgs motionArgs;
@@ -8740,7 +8711,7 @@
 
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
     ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
-    ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::INVALID, motionArgs.displayId);
 }
 
 /**
@@ -8920,97 +8891,6 @@
             WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
-    // Setup the first touch screen device.
-    prepareAxes(POSITION | ID | SLOT);
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // Create the second touch screen device, and enable multi fingers.
-    const std::string USB2 = "USB2";
-    const std::string DEVICE_NAME2 = "TOUCHSCREEN2";
-    constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-    constexpr int32_t SECOND_EVENTHUB_ID = EVENTHUB_ID + 1;
-    std::shared_ptr<InputDevice> device2 =
-            newDevice(SECOND_DEVICE_ID, DEVICE_NAME2, USB2, SECOND_EVENTHUB_ID,
-                      ftl::Flags<InputDeviceClass>(0));
-
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_X, RAW_X_MIN, RAW_X_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_POSITION_Y, RAW_Y_MIN, RAW_Y_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_TRACKING_ID, RAW_ID_MIN, RAW_ID_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->addAbsoluteAxis(SECOND_EVENTHUB_ID, ABS_MT_SLOT, RAW_SLOT_MIN, RAW_SLOT_MAX,
-                                   /*flat=*/0, /*fuzz=*/0);
-    mFakeEventHub->setAbsoluteAxisValue(SECOND_EVENTHUB_ID, ABS_MT_SLOT, /*value=*/0);
-    mFakeEventHub->addConfigurationProperty(SECOND_EVENTHUB_ID, String8("touch.deviceType"),
-                                            String8("touchScreen"));
-
-    // Setup the second touch screen device.
-    device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
-    MultiTouchInputMapper& mapper2 = device2->constructAndAddMapper<
-            MultiTouchInputMapper>(SECOND_EVENTHUB_ID, mFakePolicy->getReaderConfiguration());
-    std::list<NotifyArgs> unused =
-            device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
-                               /*changes=*/{});
-    unused += device2->reset(ARBITRARY_TIME);
-
-    // Setup PointerController.
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-
-    // Setup policy for associated displays and show touches.
-    const uint8_t hdmi1 = 0;
-    const uint8_t hdmi2 = 1;
-    mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
-    mFakePolicy->addInputPortAssociation(USB2, hdmi2);
-    mFakePolicy->setShowTouches(true);
-
-    // Create displays.
-    prepareDisplay(ui::ROTATION_0, hdmi1);
-    prepareSecondaryDisplay(ViewportType::EXTERNAL, hdmi2);
-
-    // 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);
-
-    // Two fingers down at default display.
-    int32_t x1 = 100, y1 = 125, x2 = 300, y2 = 500;
-    processPosition(mapper, x1, y1);
-    processId(mapper, 1);
-    processSlot(mapper, 1);
-    processPosition(mapper, x2, y2);
-    processId(mapper, 2);
-    processSync(mapper);
-
-    std::map<int32_t, std::vector<int32_t>>::const_iterator iter =
-            fakePointerController->getSpots().find(DISPLAY_ID);
-    ASSERT_TRUE(iter != fakePointerController->getSpots().end());
-    ASSERT_EQ(size_t(2), iter->second.size());
-
-    // Two fingers down at second display.
-    processPosition(mapper2, x1, y1);
-    processId(mapper2, 1);
-    processSlot(mapper2, 1);
-    processPosition(mapper2, x2, y2);
-    processId(mapper2, 2);
-    processSync(mapper2);
-
-    iter = fakePointerController->getSpots().find(SECONDARY_DISPLAY_ID);
-    ASSERT_TRUE(iter != fakePointerController->getSpots().end());
-    ASSERT_EQ(size_t(2), iter->second.size());
-
-    // 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);
-
-    ASSERT_TRUE(fakePointerController->getSpots().empty());
-}
-
 TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) {
     prepareAxes(POSITION);
     addConfigurationProperty("touch.deviceType", "touchScreen");
@@ -9046,7 +8926,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -9071,7 +8951,7 @@
 
     // Test all 4 orientations
     for (ui::Rotation orientation : ftl::enum_range<ui::Rotation>()) {
-        SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
+        SCOPED_TRACE(StringPrintf("Orientation %s", ftl::enum_string(orientation).c_str()));
         clearViewports();
         prepareDisplay(orientation);
         std::vector<TouchVideoFrame> frames{frame};
@@ -9703,58 +9583,6 @@
                   WithToolType(ToolType::STYLUS))));
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
-    // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
-    // indicate stylus presence dynamically.
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(true);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    processId(mapper, FIRST_TRACKING_ID);
-    processPressure(mapper, RAW_PRESSURE_MIN);
-    processPosition(mapper, 100, 200);
-    processToolType(mapper, MT_TOOL_PEN);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_TRUE(fakePointerController->isPointerShown());
-    ASSERT_NO_FATAL_FAILURE(
-            fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
-    addConfigurationProperty("touch.deviceType", "touchScreen");
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
-    // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
-    // indicate stylus presence dynamically.
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setStylusPointerIconEnabled(false);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    processId(mapper, FIRST_TRACKING_ID);
-    processPressure(mapper, RAW_PRESSURE_MIN);
-    processPosition(mapper, 100, 200);
-    processToolType(mapper, MT_TOOL_PEN);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
-            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
-                  WithToolType(ToolType::STYLUS),
-                  WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
-    ASSERT_FALSE(fakePointerController->isPointerShown());
-}
-
 // --- MultiTouchInputMapperTest_ExternalDevice ---
 
 class MultiTouchInputMapperTest_ExternalDevice : public MultiTouchInputMapperTest {
@@ -9780,7 +9608,7 @@
     processPosition(mapper, 100, 100);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
-    ASSERT_EQ(ADISPLAY_ID_DEFAULT, motionArgs.displayId);
+    ASSERT_EQ(ui::LogicalDisplayId::DEFAULT, motionArgs.displayId);
 
     // Expect the event to be sent to the external viewport if it is present.
     prepareSecondaryDisplay(ViewportType::EXTERNAL);
@@ -9790,168 +9618,15 @@
     ASSERT_EQ(SECONDARY_DISPLAY_ID, motionArgs.displayId);
 }
 
-TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
-    // we need a pointer controller for mouse mode of touchpad (start pointer at 0,0)
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(0, 0);
-
-    // prepare device and capture
+// TODO(b/281840344): Remove the test when the old touchpad stack is removed. It is currently
+//  unclear what the behavior of the touchpad logic in TouchInputMapper should do after the
+//  PointerChoreographer refactor.
+TEST_F(MultiTouchInputMapperTest, DISABLED_Process_TouchpadPointer) {
+    // prepare device
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerCapture(true);
-    mFakePolicy->setPointerController(fakePointerController);
-    MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
-
-    // captured touchpad should be a touchpad source
-    NotifyDeviceResetArgs resetArgs;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-
-    InputDeviceInfo deviceInfo = mDevice->getDeviceInfo();
-
-    const InputDeviceInfo::MotionRange* relRangeX =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeX, nullptr);
-    ASSERT_EQ(relRangeX->min, -(RAW_X_MAX - RAW_X_MIN));
-    ASSERT_EQ(relRangeX->max, RAW_X_MAX - RAW_X_MIN);
-    const InputDeviceInfo::MotionRange* relRangeY =
-            deviceInfo.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
-    ASSERT_NE(relRangeY, nullptr);
-    ASSERT_EQ(relRangeY->min, -(RAW_Y_MAX - RAW_Y_MIN));
-    ASSERT_EQ(relRangeY->max, RAW_Y_MAX - RAW_Y_MIN);
-
-    // run captured pointer tests - note that this is unscaled, so input listener events should be
-    //                              identical to what the hardware sends (accounting for any
-    //                              calibration).
-    // FINGER 0 DOWN
-    processSlot(mapper, 0);
-    processId(mapper, 1);
-    processPosition(mapper, 100 + RAW_X_MIN, 100 + RAW_Y_MIN);
-    processKey(mapper, BTN_TOUCH, 1);
-    processSync(mapper);
-
-    // expect coord[0] to contain initial location of touch 0
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 DOWN
-    processSlot(mapper, 1);
-    processId(mapper, 2);
-    processPosition(mapper, 560 + RAW_X_MIN, 154 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
-    ASSERT_EQ(2U, args.getPointerCount());
-    ASSERT_EQ(0, args.pointerProperties[0].id);
-    ASSERT_EQ(1, args.pointerProperties[1].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 560, 154, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 MOVE
-    processPosition(mapper, 540 + RAW_X_MIN, 690 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
-    // from move
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 100, 100, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 0 MOVE
-    processSlot(mapper, 0);
-    processPosition(mapper, 50 + RAW_X_MIN, 800 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new touch 0 location, coord[1] to contain previous location
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 50, 800, 1, 0, 0, 0, 0, 0, 0, 0));
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[1], 540, 690, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // BUTTON DOWN
-    processKey(mapper, BTN_LEFT, 1);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move before button press
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, args.action);
-
-    // BUTTON UP
-    processKey(mapper, BTN_LEFT, 0);
-    processSync(mapper);
-
-    // touchinputmapper design sends a move after button release
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, args.action);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-
-    // FINGER 0 UP
-    processId(mapper, -1);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP | 0x0000, args.action);
-
-    // FINGER 1 MOVE
-    processSlot(mapper, 1);
-    processPosition(mapper, 320 + RAW_X_MIN, 900 + RAW_Y_MIN);
-    processSync(mapper);
-
-    // expect coord[0] to contain new location of touch 1, and properties[0].id to contain 1
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
-    ASSERT_EQ(1U, args.getPointerCount());
-    ASSERT_EQ(1, args.pointerProperties[0].id);
-    ASSERT_NO_FATAL_FAILURE(
-            assertPointerCoords(args.pointerCoords[0], 320, 900, 1, 0, 0, 0, 0, 0, 0, 0));
-
-    // FINGER 1 UP
-    processId(mapper, -1);
-    processKey(mapper, BTN_TOUCH, 0);
-    processSync(mapper);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
-
-    // non captured touchpad should be a mouse source
-    mFakePolicy->setPointerCapture(false);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
-    ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-}
-
-TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-    fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-    fakePointerController->setPosition(0, 0);
-
-    // prepare device and capture
-    prepareDisplay(ui::ROTATION_0);
-    prepareAxes(POSITION | ID | SLOT);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
     // run uncaptured pointer tests - pushes out generic events
     // FINGER 0 DOWN
@@ -10004,24 +9679,15 @@
     ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
 }
 
-TEST_F(MultiTouchInputMapperTest, WhenCapturedAndNotCaptured_GetSources) {
-    std::shared_ptr<FakePointerController> fakePointerController =
-            std::make_shared<FakePointerController>();
-
+TEST_F(MultiTouchInputMapperTest, Touchpad_GetSources) {
     prepareDisplay(ui::ROTATION_0);
     prepareAxes(POSITION | ID | SLOT);
     mFakeEventHub->addKey(EVENTHUB_ID, BTN_LEFT, 0, AKEYCODE_UNKNOWN, 0);
-    mFakePolicy->setPointerController(fakePointerController);
-    mFakePolicy->setPointerCapture(false);
+    mFakePolicy->setPointerCapture(/*window=*/nullptr);
     MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
 
     // uncaptured touchpad should be a pointer device
     ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
-
-    // captured touchpad should be a touchpad device
-    mFakePolicy->setPointerCapture(true);
-    configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
 // --- BluetoothMultiTouchInputMapperTest ---
@@ -10080,19 +9746,14 @@
     float mPointerXZoomScale;
     void preparePointerMode(int xAxisResolution, int yAxisResolution) {
         addConfigurationProperty("touch.deviceType", "pointer");
-        std::shared_ptr<FakePointerController> fakePointerController =
-                std::make_shared<FakePointerController>();
-        fakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
-        fakePointerController->setPosition(0, 0);
         prepareDisplay(ui::ROTATION_0);
 
         prepareAxes(POSITION);
         prepareAbsoluteAxisResolution(xAxisResolution, yAxisResolution);
         // In order to enable swipe and freeform gesture in pointer mode, pointer capture
         // needs to be disabled, and the pointer gesture needs to be enabled.
-        mFakePolicy->setPointerCapture(false);
+        mFakePolicy->setPointerCapture(/*window=*/nullptr);
         mFakePolicy->setPointerGestureEnabled(true);
-        mFakePolicy->setPointerController(fakePointerController);
 
         float rawDiagonal = hypotf(RAW_X_MAX - RAW_X_MIN, RAW_Y_MAX - RAW_Y_MIN);
         float displayDiagonal = hypotf(DISPLAY_WIDTH, DISPLAY_HEIGHT);
@@ -10419,67 +10080,6 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-// --- JoystickInputMapperTest ---
-
-class JoystickInputMapperTest : public InputMapperTest {
-protected:
-    static const int32_t RAW_X_MIN;
-    static const int32_t RAW_X_MAX;
-    static const int32_t RAW_Y_MIN;
-    static const int32_t RAW_Y_MAX;
-
-    void SetUp() override {
-        InputMapperTest::SetUp(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL);
-    }
-    void prepareAxes() {
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_X, RAW_X_MIN, RAW_X_MAX, 0, 0);
-        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_Y, RAW_Y_MIN, RAW_Y_MAX, 0, 0);
-    }
-
-    void processAxis(JoystickInputMapper& mapper, int32_t axis, int32_t value) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_ABS, axis, value);
-    }
-
-    void processSync(JoystickInputMapper& mapper) {
-        process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
-    }
-
-    void prepareVirtualDisplay(ui::Rotation orientation) {
-        setDisplayInfoAndReconfigure(VIRTUAL_DISPLAY_ID, VIRTUAL_DISPLAY_WIDTH,
-                                     VIRTUAL_DISPLAY_HEIGHT, orientation, VIRTUAL_DISPLAY_UNIQUE_ID,
-                                     NO_PORT, ViewportType::VIRTUAL);
-    }
-};
-
-const int32_t JoystickInputMapperTest::RAW_X_MIN = -32767;
-const int32_t JoystickInputMapperTest::RAW_X_MAX = 32767;
-const int32_t JoystickInputMapperTest::RAW_Y_MIN = -32767;
-const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767;
-
-TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
-    prepareAxes();
-    JoystickInputMapper& mapper = constructAndAddMapper<JoystickInputMapper>();
-
-    mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, VIRTUAL_DISPLAY_UNIQUE_ID);
-
-    prepareVirtualDisplay(ui::ROTATION_0);
-
-    // Send an axis event
-    processAxis(mapper, ABS_X, 100);
-    processSync(mapper);
-
-    NotifyMotionArgs args;
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
-
-    // Send another axis event
-    processAxis(mapper, ABS_Y, 100);
-    processSync(mapper);
-
-    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
-    ASSERT_EQ(VIRTUAL_DISPLAY_ID, args.displayId);
-}
-
 // --- PeripheralControllerTest ---
 
 class PeripheralControllerTest : public testing::Test {
@@ -10598,6 +10198,28 @@
     ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_BRIGHTNESS);
 }
 
+TEST_F(LightControllerTest, MonoKeyboardMuteLight) {
+    RawLightInfo infoMono = {.id = 1,
+                             .name = "mono_keyboard_mute",
+                             .maxBrightness = 255,
+                             .flags = InputLightClass::BRIGHTNESS |
+                                     InputLightClass::KEYBOARD_MIC_MUTE,
+                             .path = ""};
+    mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
+
+    PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+    std::list<NotifyArgs> unused =
+            mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+                               /*changes=*/{});
+
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    std::vector<InputDeviceLightInfo> lights = info.getLights();
+    ASSERT_EQ(1U, lights.size());
+    ASSERT_EQ(InputDeviceLightType::KEYBOARD_MIC_MUTE, lights[0].type);
+    ASSERT_EQ(0U, lights[0].preferredBrightnessLevels.size());
+}
+
 TEST_F(LightControllerTest, MonoKeyboardBacklight) {
     RawLightInfo infoMono = {.id = 1,
                              .name = "mono_keyboard_backlight",
@@ -10872,24 +10494,24 @@
     ASSERT_EQ(controller.getLightColor(lights[0].id).value_or(-1), LIGHT_COLOR);
 }
 
-TEST_F(LightControllerTest, PlayerIdLight) {
+TEST_F(LightControllerTest, SonyPlayerIdLight) {
     RawLightInfo info1 = {.id = 1,
-                          .name = "player1",
+                          .name = "sony1",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info2 = {.id = 2,
-                          .name = "player2",
+                          .name = "sony2",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info3 = {.id = 3,
-                          .name = "player3",
+                          .name = "sony3",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
     RawLightInfo info4 = {.id = 4,
-                          .name = "player4",
+                          .name = "sony4",
                           .maxBrightness = 255,
                           .flags = InputLightClass::BRIGHTNESS,
                           .path = ""};
@@ -10903,6 +10525,49 @@
     controller.populateDeviceInfo(&info);
     std::vector<InputDeviceLightInfo> lights = info.getLights();
     ASSERT_EQ(1U, lights.size());
+    ASSERT_STREQ("sony", lights[0].name.c_str());
+    ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type);
+    ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS));
+    ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB));
+
+    ASSERT_FALSE(controller.setLightColor(lights[0].id, LIGHT_COLOR));
+    ASSERT_TRUE(controller.setLightPlayerId(lights[0].id, LIGHT_PLAYER_ID));
+    ASSERT_EQ(controller.getLightPlayerId(lights[0].id).value_or(-1), LIGHT_PLAYER_ID);
+    ASSERT_STREQ("sony", lights[0].name.c_str());
+}
+
+TEST_F(LightControllerTest, PlayerIdLight) {
+    RawLightInfo info1 = {.id = 1,
+                          .name = "player-1",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info2 = {.id = 2,
+                          .name = "player-2",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info3 = {.id = 3,
+                          .name = "player-3",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    RawLightInfo info4 = {.id = 4,
+                          .name = "player-4",
+                          .maxBrightness = 255,
+                          .flags = InputLightClass::BRIGHTNESS,
+                          .path = ""};
+    mFakeEventHub->addRawLightInfo(info1.id, std::move(info1));
+    mFakeEventHub->addRawLightInfo(info2.id, std::move(info2));
+    mFakeEventHub->addRawLightInfo(info3.id, std::move(info3));
+    mFakeEventHub->addRawLightInfo(info4.id, std::move(info4));
+
+    PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    std::vector<InputDeviceLightInfo> lights = info.getLights();
+    ASSERT_EQ(1U, lights.size());
+    ASSERT_STREQ("player", lights[0].name.c_str());
     ASSERT_EQ(InputDeviceLightType::PLAYER_ID, lights[0].type);
     ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS));
     ASSERT_FALSE(lights[0].capabilityFlags.test(InputDeviceLightCapability::RGB));
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000..db4e761
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2024 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 "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+using perfetto::protos::pbzero::WinscopeExtensions;
+using perfetto::protos::pbzero::WinscopeExtensionsImpl;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+                                const std::variant<KeyEvent, MotionEvent>& event) {
+    std::visit([&](const auto& e) { out << e; }, event);
+    return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+                                const InputTraceSession::WindowDispatchEvent& event) {
+    out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+    return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+    return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+        const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+    protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+    configure(inputEventConfig);
+
+    perfetto::TraceConfig config;
+    config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+    auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+    dataSourceConfig->set_name("android.input.inputevent");
+    dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+    std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+    tracingSession->Setup(config);
+    tracingSession->StartBlocking();
+    return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+    tracingSession->StopBlocking();
+    std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+    return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+    using namespace perfetto::protos::pbzero;
+
+    ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+    ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+    ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+    Trace::Decoder trace{rawTrace};
+    if (trace.has_packet()) {
+        for (auto it = trace.packet(); it; it++) {
+            TracePacket::Decoder packet{it->as_bytes()};
+            if (!packet.has_winscope_extensions()) {
+                continue;
+            }
+
+            WinscopeExtensions::Decoder extensions{packet.winscope_extensions()};
+            const auto& field =
+                    extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber);
+            if (!field.valid()) {
+                continue;
+            }
+
+            EXPECT_TRUE(packet.has_timestamp());
+            EXPECT_TRUE(packet.has_timestamp_clock_id());
+            EXPECT_EQ(packet.timestamp_clock_id(),
+                      static_cast<uint32_t>(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC));
+
+            AndroidInputEvent::Decoder event{field.as_bytes()};
+            if (event.has_dispatcher_motion_event()) {
+                tracedMotions.emplace_back(event.dispatcher_motion_event(),
+                                           /*redacted=*/false);
+            }
+            if (event.has_dispatcher_motion_event_redacted()) {
+                tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+                                           /*redacted=*/true);
+            }
+            if (event.has_dispatcher_key_event()) {
+                tracedKeys.emplace_back(event.dispatcher_key_event(),
+                                        /*redacted=*/false);
+            }
+            if (event.has_dispatcher_key_event_redacted()) {
+                tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+                                        /*redacted=*/true);
+            }
+            if (event.has_dispatcher_window_dispatch_event()) {
+                tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+                                                    /*redacted=*/false);
+            }
+            if (event.has_dispatcher_window_dispatch_event_redacted()) {
+                tracedWindowDispatches
+                        .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+                                      /*redacted=*/true);
+            }
+        }
+    }
+    return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+                      std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+                  const AndroidWindowInputDispatchEvent::Decoder& traced) {
+    return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+            expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+                                const TracedEvents& tracedEvents, std::string_view name) {
+    uint32_t totalExpectedCount = 0;
+
+    for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+        int32_t totalMatchCount = 0;
+        int32_t redactedMatchCount = 0;
+        for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+            if (eventMatches(expectedEvent, tracedEvent)) {
+                totalMatchCount++;
+                if (isRedacted) {
+                    redactedMatchCount++;
+                }
+            }
+        }
+        switch (expectedLevel) {
+            case Level::NONE:
+                ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+                                              << "\n\tExpected event: " << expectedEvent;
+                break;
+            case Level::REDACTED:
+            case Level::COMPLETE:
+                ASSERT_EQ(totalMatchCount, 1)
+                        << "Event should match exactly one traced event, but it matched: "
+                        << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+                ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+                totalExpectedCount++;
+                break;
+        }
+    }
+
+    ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+            << "The number of traced " << name
+            << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+        std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+      : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+    const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+    verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+    mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+    mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+    mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+    auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+    verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+    verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+    verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+                               "window dispatch");
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000..bda5521
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2024 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 "FakeWindows.h"
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/config/android/android_input_event_config.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <perfetto/tracing.h>
+#include <variant>
+#include <vector>
+
+namespace android {
+
+/**
+ * Tracing level constants used for adding expectations to the InputTraceSession.
+ */
+enum class Level {
+    NONE,
+    REDACTED,
+    COMPLETE,
+};
+
+template <typename K, typename V>
+using ArrayMap = std::vector<std::pair<K, V>>;
+
+/**
+ * A scoped representation of a tracing session that is used to make assertions on the trace.
+ *
+ * When the trace session is created, an "android.input.inputevent" trace will be started
+ * synchronously with the given configuration. While the trace is ongoing, the caller must
+ * specify the events that are expected to be in the trace using the expect* methods.
+ *
+ * When the session is destroyed, the trace is stopped synchronously, and all expectations will
+ * be verified using the gtest framework. This acts as a strict verifier, where the verification
+ * will fail both if an expected event does not show up in the trace and if there is an extra
+ * event in the trace that was not expected. Ordering is NOT verified for any events.
+ */
+class InputTraceSession {
+public:
+    explicit InputTraceSession(
+            std::function<void(
+                    protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)>
+                    configure);
+
+    ~InputTraceSession();
+
+    void expectMotionTraced(Level level, const MotionEvent& event);
+
+    void expectKeyTraced(Level level, const KeyEvent& event);
+
+    struct WindowDispatchEvent {
+        std::variant<KeyEvent, MotionEvent> event;
+        sp<FakeWindowHandle> window;
+    };
+    void expectDispatchTraced(Level level, const WindowDispatchEvent& event);
+
+private:
+    std::unique_ptr<perfetto::TracingSession> mPerfettoSession;
+    ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches;
+    ArrayMap<MotionEvent, Level> mExpectedMotions;
+    ArrayMap<KeyEvent, Level> mExpectedKeys;
+
+    void verifyExpectations(const std::string& rawTrace);
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
new file mode 100644
index 0000000..3cc4bdd
--- /dev/null
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../InputCommonConverter.h"
+#include "../dispatcher/InputDispatcher.h"
+#include "../dispatcher/trace/InputTracingPerfettoBackend.h"
+#include "../dispatcher/trace/ThreadedBackend.h"
+#include "FakeApplicationHandle.h"
+#include "FakeInputDispatcherPolicy.h"
+#include "FakeWindows.h"
+#include "InputTraceSession.h"
+#include "TestEventMatchers.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <android/content/pm/IPackageManagerNative.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <perfetto/trace/android/android_input_event.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions.pbzero.h>
+#include <perfetto/trace/android/winscope_extensions_impl.pbzero.h>
+#include <perfetto/trace/trace.pbzero.h>
+#include <private/android_filesystem_config.h>
+#include <map>
+#include <vector>
+
+namespace android::inputdispatcher::trace {
+
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+
+namespace {
+
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN));
+static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) ==
+              static_cast<int32_t>(AKEY_EVENT_ACTION_UP));
+constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN;
+constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE;
+constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP;
+constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL;
+
+constexpr gui::Pid PID{1};
+
+constexpr gui::Uid ALLOWED_UID_1{10012};
+constexpr gui::Uid ALLOWED_UID_2{10013};
+constexpr gui::Uid DISALLOWED_UID_1{1};
+constexpr gui::Uid DISALLOWED_UID_2{99};
+constexpr gui::Uid UNLISTED_UID{12345};
+
+const std::string ALLOWED_PKG_1{"allowed.pkg.1"};
+const std::string ALLOWED_PKG_2{"allowed.pkg.2"};
+const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"};
+const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"};
+
+const std::map<std::string, gui::Uid> kPackageUidMap{
+        {ALLOWED_PKG_1, ALLOWED_UID_1},
+        {ALLOWED_PKG_2, ALLOWED_UID_2},
+        {DISALLOWED_PKG_1, DISALLOWED_UID_1},
+        {DISALLOWED_PKG_2, DISALLOWED_UID_2},
+};
+
+class FakePackageManager : public content::pm::IPackageManagerNativeDefault {
+public:
+    binder::Status getPackageUid(const ::std::string& pkg, int64_t flags, int32_t userId,
+            int32_t* outUid) override {
+        auto it = kPackageUidMap.find(pkg);
+        *outUid = it != kPackageUidMap.end() ? static_cast<int32_t>(it->second.val()) : -1;
+        return binder::Status::ok();
+    }
+};
+
+const sp<testing::NiceMock<FakePackageManager>> kPackageManager =
+        sp<testing::NiceMock<FakePackageManager>>::make();
+
+const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>();
+
+} // namespace
+
+// --- InputTracingTest ---
+
+class InputTracingTest : public testing::Test {
+protected:
+    std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
+    std::unique_ptr<InputDispatcher> mDispatcher;
+
+    void SetUp() override {
+        impl::PerfettoBackend::sUseInProcessBackendForTest = true;
+        impl::PerfettoBackend::sPackageManagerProvider = []() { return kPackageManager; };
+        mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+
+        auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>(
+                impl::PerfettoBackend());
+        mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting();
+        mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend));
+
+        mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+        ASSERT_EQ(OK, mDispatcher->start());
+    }
+
+    void TearDown() override {
+        ASSERT_EQ(OK, mDispatcher->stop());
+        mDispatcher.reset();
+        mFakePolicy.reset();
+    }
+
+    void waitForTracerIdle() {
+        mDispatcher->waitForIdle();
+        mRequestTracerIdle();
+    }
+
+    void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) {
+        gui::FocusRequest request;
+        request.token = window->getToken();
+        request.windowName = window->getName();
+        request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC);
+        request.displayId = window->getInfo()->displayId.val();
+        mDispatcher->setFocusedWindow(request);
+    }
+
+    void tapAndExpect(const std::vector<sp<FakeWindowHandle>>& windows, Level inboundTraceLevel,
+                      Level dispatchTraceLevel, InputTraceSession& s) {
+        const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                                  .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                  .build();
+        mDispatcher->notifyMotion(down);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                                .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                                .build();
+        mDispatcher->notifyMotion(up);
+        s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+    void keypressAndExpect(const std::vector<sp<FakeWindowHandle>>& windows,
+                           Level inboundTraceLevel, Level dispatchTraceLevel,
+                           InputTraceSession& s) {
+        const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(down);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+
+        const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build();
+        mDispatcher->notifyKey(up);
+        s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up));
+        for (const auto& window : windows) {
+            auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP));
+            s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window});
+        }
+    }
+
+private:
+    std::function<void()> mRequestTracerIdle;
+};
+
+TEST_F(InputTracingTest, EmptyConfigTracesNothing) {
+    InputTraceSession s{[](auto& config) {}};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceAll) {
+    InputTraceSession s{
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, NoRulesTracesNothing) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, EmptyRuleMatchesEverything) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, UnspecifiedTracelLevel) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match everything, trace level unspecified
+        auto rule = config->add_rules();
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    // Event is not traced by default if trace level is unspecified
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+    keypressAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchSecureWindow) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match secure windows as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since neither are secure windows, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    // Events should be matched as secure if any of the target windows is marked as secure.
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(true);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setSecure(false);
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchImeConnectionActive) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match IME Connection Active as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_ime_connection_active(true);
+    }};
+
+    // Add a normal window and a spy window.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Since IME connection is not active, events should not be traced.
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAllPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+    }};
+
+    // All windows are allowlisted.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM});
+    systemSpy->setSpy(true);
+    systemSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add a disallowed spy. This will result in the event not being traced for all windows.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Change the owner of the disallowed spy to one for which we don't have a package mapping.
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(),
+                                        *disallowedSpy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Remove the disallowed spy. Events are traced again.
+    mDispatcher->onWindowInfosChanged(
+            {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MatchAnyPackages) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match any package as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(ALLOWED_PKG_2);
+    }};
+
+    // Just a disallowed window. Events are not traced.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Add a spy for which we don't have a package mapping. Events are still not traced.
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, UNLISTED_UID);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Add an allowed spy. Events are now traced for all packages.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged(
+            {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Add another disallowed spy. Events are still traced.
+    auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2);
+    disallowedSpy2->setSpy(true);
+    disallowedSpy2->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(),
+                                        *spy->getInfo(), *window->getInfo()},
+                                       {},
+                                       0,
+                                       0});
+
+    tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleMatchersInOneRule) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Match all of the following conditions as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->add_match_all_packages(ALLOWED_PKG_1);
+        rule->add_match_all_packages(ALLOWED_PKG_2);
+        rule->add_match_any_packages(ALLOWED_PKG_1);
+        rule->add_match_any_packages(DISALLOWED_PKG_1);
+        rule->set_match_secure(false);
+        rule->set_match_ime_connection_active(false);
+    }};
+
+    // A single window into an allowed UID. Matches all matchers.
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Secure window does not match.
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // IME Connection Active does not match.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    mDispatcher->setInputMethodConnectionIsActive(true);
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    disallowedSpy->setSpy(true);
+    disallowedSpy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s);
+
+    // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages.
+    window->setOwnerInfo(PID, ALLOWED_UID_2);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::NONE, Level::NONE, s);
+
+    // All conditions match.
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_1);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, MultipleRulesMatchInOrder) {
+    InputTraceSession s{[](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Don't trace secure events
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE);
+        rule1->set_match_secure(true);
+        // Rule: Trace matched packages as COMPLETE when IME inactive
+        auto rule2 = config->add_rules();
+        rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule2->add_match_all_packages(ALLOWED_PKG_1);
+        rule2->add_match_all_packages(ALLOWED_PKG_2);
+        rule2->set_match_ime_connection_active(false);
+        // Rule: Trace the rest of the events as REDACTED
+        auto rule3 = config->add_rules();
+        rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    // Verify that the first rule that matches in the order that they are specified is the
+    // one that applies to the event.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s);
+
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID);
+    spy->setOwnerInfo(PID, ALLOWED_UID_2);
+    spy->setSpy(true);
+    spy->setTrustedOverlay(true);
+    spy->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::NONE, Level::NONE, s);
+
+    spy->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s);
+
+    spy->setOwnerInfo(PID, DISALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s);
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceInboundEvents) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace inbounds events - don't trace window dispatch
+        config->set_trace_dispatcher_input_events(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only the inbound events are traced. No dispatch events are traced.
+    tapAndExpect({window}, Level::REDACTED, Level::NONE, s);
+
+    // Notify a down event, which should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    // Force a cancel event to be synthesized. This should not be traced, because only inbound
+    // events are requested.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+TEST_F(InputTracingTest, TraceWindowDispatch) {
+    InputTraceSession s{[](auto& config) {
+        // Only trace window dispatch - don't trace event details
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace everything as REDACTED
+        auto rule1 = config->add_rules();
+        rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+    }};
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    window->setOwnerInfo(PID, ALLOWED_UID_1);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+    // Only dispatch events are traced. No inbound events are traced.
+    tapAndExpect({window}, Level::NONE, Level::REDACTED, s);
+
+    // Notify a down event; the dispatch should be traced.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    s.expectMotionTraced(Level::NONE, toMotionEvent(down));
+    mDispatcher->notifyMotion(down);
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    // Force a cancel event to be synthesized. All events that are dispatched should be traced.
+    mDispatcher->cancelCurrentTouch();
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+    s.expectMotionTraced(Level::NONE, *consumed);
+    s.expectDispatchTraced(Level::REDACTED, {*consumed, window});
+
+    waitForTracerIdle();
+}
+
+// TODO(b/336097719): Investigate flakiness and re-enable this test.
+TEST_F(InputTracingTest, DISABLED_SimultaneousTracingSessions) {
+    auto s1 = std::make_unique<InputTraceSession>(
+            [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); });
+
+    auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true);
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    auto s2 = std::make_unique<InputTraceSession>([](auto& config) {
+        config->set_trace_dispatcher_input_events(true);
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace all events as REDACTED when IME inactive
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED);
+        rule->set_match_ime_connection_active(false);
+    });
+
+    auto s3 = std::make_unique<InputTraceSession>([](auto& config) {
+        // Only trace window dispatch
+        config->set_trace_dispatcher_window_dispatch(true);
+        config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES);
+        // Rule: Trace non-secure events as COMPLETE
+        auto rule = config->add_rules();
+        rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE);
+        rule->set_match_secure(false);
+    });
+
+    // Down event should be recorded on all traces.
+    const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                              .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                              .build();
+    mDispatcher->notifyMotion(down);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(down));
+    auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event when IME is active.
+    mDispatcher->setInputMethodConnectionIsActive(true);
+    const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move1);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1));
+    s2->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move1));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::NONE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    // Move event after window became secure.
+    mDispatcher->setInputMethodConnectionIsActive(false);
+    window->setSecure(true);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                               .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                               .build();
+    mDispatcher->notifyMotion(move2);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2));
+    s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(move2));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s2->expectDispatchTraced(Level::REDACTED, {*consumed, window});
+    s3->expectDispatchTraced(Level::NONE, {*consumed, window});
+
+    waitForTracerIdle();
+    s2.reset();
+
+    // Up event.
+    window->setSecure(false);
+    mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+    const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+                            .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
+                            .build();
+    mDispatcher->notifyMotion(up);
+    s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up));
+    s3->expectMotionTraced(Level::NONE, toMotionEvent(up));
+    consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+    s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+    s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window});
+
+    waitForTracerIdle();
+    s3.reset();
+
+    tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+    keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1);
+
+    waitForTracerIdle();
+    s1.reset();
+}
+
+} // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index db89168..f41b39a 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -26,9 +26,11 @@
 #include <vector>
 
 #include <EventHub.h>
+#include <InputDevice.h>
 #include <InputReaderBase.h>
+#include <InputReaderContext.h>
 #include <NotifyArgs.h>
-#include <PointerControllerInterface.h>
+#include <PointerChoreographerPolicyInterface.h>
 #include <StylusState.h>
 #include <VibrationElement.h>
 #include <android-base/logging.h>
@@ -37,6 +39,7 @@
 #include <input/InputDevice.h>
 #include <input/KeyCharacterMap.h>
 #include <input/KeyLayoutMap.h>
+#include <input/KeyboardClassifier.h>
 #include <input/PropertyMap.h>
 #include <input/TouchVideoFrame.h>
 #include <input/VirtualKeyMap.h>
@@ -54,14 +57,10 @@
     MOCK_METHOD(bool, shouldDropVirtualKey, (nsecs_t now, int32_t keyCode, int32_t scanCode),
                 (override));
 
-    MOCK_METHOD(void, fadePointer, (), (override));
-    MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, getPointerController,
-                (int32_t deviceId), (override));
-
     MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
     int32_t bumpGeneration() override { return ++mGeneration; }
 
-    MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
+    MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo>& outDevices),
                 (override));
     MOCK_METHOD(std::list<NotifyArgs>, dispatchExternalStylusState, (const StylusState& outState),
                 (override));
@@ -80,8 +79,11 @@
     MOCK_METHOD(void, setLastKeyDownTimestamp, (nsecs_t when));
     MOCK_METHOD(nsecs_t, getLastKeyDownTimestamp, ());
 
+    KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; };
+
 private:
     int32_t mGeneration = 0;
+    std::unique_ptr<KeyboardClassifier> mClassifier = std::make_unique<KeyboardClassifier>();
 };
 
 class MockEventHubInterface : public EventHubInterface {
@@ -90,12 +92,13 @@
     MOCK_METHOD(InputDeviceIdentifier, getDeviceIdentifier, (int32_t deviceId), (const));
     MOCK_METHOD(int32_t, getDeviceControllerNumber, (int32_t deviceId), (const));
     MOCK_METHOD(std::optional<PropertyMap>, getConfiguration, (int32_t deviceId), (const));
-    MOCK_METHOD(status_t, getAbsoluteAxisInfo,
-                (int32_t deviceId, int axis, RawAbsoluteAxisInfo* outAxisInfo), (const));
+    MOCK_METHOD(std::optional<RawAbsoluteAxisInfo>, getAbsoluteAxisInfo,
+                (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
     MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
-    MOCK_METHOD(void, addKeyRemapping, (int32_t deviceId, int fromKeyCode, int toKeyCode), (const));
+    MOCK_METHOD(void, setKeyRemapping,
+                (int32_t deviceId, (const std::map<int32_t, int32_t>& keyRemapping)), (const));
     MOCK_METHOD(status_t, mapKey,
                 (int32_t deviceId, int scanCode, int usageCode, int32_t metaState,
                  int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags),
@@ -130,7 +133,7 @@
     MOCK_METHOD(int32_t, getKeyCodeState, (int32_t deviceId, int32_t keyCode), (const, override));
     MOCK_METHOD(int32_t, getSwitchState, (int32_t deviceId, int32_t sw), (const, override));
 
-    MOCK_METHOD(status_t, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis, int32_t* outValue),
+    MOCK_METHOD(std::optional<int32_t>, getAbsoluteAxisValue, (int32_t deviceId, int32_t axis),
                 (const, override));
     MOCK_METHOD(base::Result<std::vector<int32_t>>, getMtSlotValues,
                 (int32_t deviceId, int32_t axis, size_t slotCount), (const, override));
@@ -171,7 +174,7 @@
     MOCK_METHOD(void, requestReopenDevices, (), (override));
     MOCK_METHOD(void, wake, (), (override));
 
-    MOCK_METHOD(void, dump, (std::string & dump), (const, override));
+    MOCK_METHOD(void, dump, (std::string& dump), (const, override));
     MOCK_METHOD(void, monitor, (), (const, override));
     MOCK_METHOD(bool, isDeviceEnabled, (int32_t deviceId), (const, override));
     MOCK_METHOD(status_t, enableDevice, (int32_t deviceId), (override));
@@ -179,4 +182,84 @@
     MOCK_METHOD(void, sysfsNodeChanged, (const std::string& sysfsNodePath), (override));
 };
 
+class MockPointerChoreographerPolicyInterface : public PointerChoreographerPolicyInterface {
+public:
+    MOCK_METHOD(std::shared_ptr<PointerControllerInterface>, createPointerController,
+                (PointerControllerInterface::ControllerType), (override));
+    MOCK_METHOD(void, notifyPointerDisplayIdChanged,
+                (ui::LogicalDisplayId displayId, const FloatPoint& position), (override));
+    MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
+    MOCK_METHOD(void, notifyMouseCursorFadedOnTyping, (), (override));
+};
+
+class MockInputDevice : public InputDevice {
+public:
+    MockInputDevice(InputReaderContext* context, int32_t id, int32_t generation,
+                    const InputDeviceIdentifier& identifier)
+          : InputDevice(context, id, generation, identifier) {}
+
+    MOCK_METHOD(uint32_t, getSources, (), (const, override));
+    MOCK_METHOD(std::optional<DisplayViewport>, getAssociatedViewport, (), (const));
+    MOCK_METHOD(bool, isEnabled, (), ());
+
+    MOCK_METHOD(void, dump, (std::string& dump, const std::string& eventHubDevStr), ());
+    MOCK_METHOD(void, addEmptyEventHubDevice, (int32_t eventHubId), ());
+    MOCK_METHOD(std::list<NotifyArgs>, addEventHubDevice,
+                (nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig),
+                ());
+    MOCK_METHOD(void, removeEventHubDevice, (int32_t eventHubId), ());
+    MOCK_METHOD(std::list<NotifyArgs>, configure,
+                (nsecs_t when, const InputReaderConfiguration& readerConfig,
+                 ConfigurationChanges changes),
+                ());
+    MOCK_METHOD(std::list<NotifyArgs>, reset, (nsecs_t when), ());
+    MOCK_METHOD(std::list<NotifyArgs>, process, (const RawEvent* rawEvents, size_t count), ());
+    MOCK_METHOD(std::list<NotifyArgs>, timeoutExpired, (nsecs_t when), ());
+    MOCK_METHOD(std::list<NotifyArgs>, updateExternalStylusState, (const StylusState& state), ());
+
+    MOCK_METHOD(InputDeviceInfo, getDeviceInfo, (), ());
+    MOCK_METHOD(int32_t, getKeyCodeState, (uint32_t sourceMask, int32_t keyCode), ());
+    MOCK_METHOD(int32_t, getScanCodeState, (uint32_t sourceMask, int32_t scanCode), ());
+    MOCK_METHOD(int32_t, getSwitchState, (uint32_t sourceMask, int32_t switchCode), ());
+    MOCK_METHOD(int32_t, getKeyCodeForKeyLocation, (int32_t locationKeyCode), (const));
+    MOCK_METHOD(bool, markSupportedKeyCodes,
+                (uint32_t sourceMask, const std::vector<int32_t>& keyCodes, uint8_t* outFlags), ());
+    MOCK_METHOD(std::list<NotifyArgs>, vibrate,
+                (const VibrationSequence& sequence, ssize_t repeat, int32_t token), ());
+    MOCK_METHOD(std::list<NotifyArgs>, cancelVibrate, (int32_t token), ());
+    MOCK_METHOD(bool, isVibrating, (), ());
+    MOCK_METHOD(std::vector<int32_t>, getVibratorIds, (), ());
+    MOCK_METHOD(std::list<NotifyArgs>, cancelTouch, (nsecs_t when, nsecs_t readTime), ());
+    MOCK_METHOD(bool, enableSensor,
+                (InputDeviceSensorType sensorType, std::chrono::microseconds samplingPeriod,
+                 std::chrono::microseconds maxBatchReportLatency),
+                ());
+
+    MOCK_METHOD(void, disableSensor, (InputDeviceSensorType sensorType), ());
+    MOCK_METHOD(void, flushSensor, (InputDeviceSensorType sensorType), ());
+
+    MOCK_METHOD(std::optional<int32_t>, getBatteryEventHubId, (), (const));
+
+    MOCK_METHOD(bool, setLightColor, (int32_t lightId, int32_t color), ());
+    MOCK_METHOD(bool, setLightPlayerId, (int32_t lightId, int32_t playerId), ());
+    MOCK_METHOD(std::optional<int32_t>, getLightColor, (int32_t lightId), ());
+    MOCK_METHOD(std::optional<int32_t>, getLightPlayerId, (int32_t lightId), ());
+
+    MOCK_METHOD(int32_t, getMetaState, (), ());
+    MOCK_METHOD(void, updateMetaState, (int32_t keyCode), ());
+
+    MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ());
+
+    MOCK_METHOD(void, bumpGeneration, (), ());
+
+    MOCK_METHOD(const PropertyMap&, getConfiguration, (), (const, override));
+
+    MOCK_METHOD(NotifyDeviceResetArgs, notifyReset, (nsecs_t when), ());
+
+    MOCK_METHOD(std::optional<ui::LogicalDisplayId>, getAssociatedDisplayId, (), ());
+
+    MOCK_METHOD(void, updateLedState, (bool reset), ());
+
+    MOCK_METHOD(size_t, getMapperCount, (), ());
+};
 } // namespace android
diff --git a/services/inputflinger/tests/JoystickInputMapper_test.cpp b/services/inputflinger/tests/JoystickInputMapper_test.cpp
new file mode 100644
index 0000000..adebd72
--- /dev/null
+++ b/services/inputflinger/tests/JoystickInputMapper_test.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2024 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 "JoystickInputMapper.h"
+
+#include <list>
+#include <optional>
+
+#include <EventHub.h>
+#include <NotifyArgs.h>
+#include <ftl/flags.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <ui/LogicalDisplayId.h>
+
+#include "InputMapperTest.h"
+#include "TestConstants.h"
+#include "TestEventMatchers.h"
+
+namespace android {
+
+using namespace ftl::flag_operators;
+using testing::ElementsAre;
+using testing::IsEmpty;
+using testing::Return;
+using testing::VariantWith;
+
+class JoystickInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
+                .WillRepeatedly(Return(InputDeviceClass::JOYSTICK | InputDeviceClass::EXTERNAL));
+
+        // The mapper requests info on all ABS axis IDs, including ones which aren't actually used
+        // (e.g. in the range from 0x0b (ABS_BRAKE) to 0x0f (ABS_HAT0X)), so just return nullopt for
+        // all axes we don't explicitly set up below.
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, testing::_))
+                .WillRepeatedly(Return(std::nullopt));
+
+        setupAxis(ABS_X, /*valid=*/true, /*min=*/-32767, /*max=*/32767, /*resolution=*/0);
+        setupAxis(ABS_Y, /*valid=*/true, /*min=*/-32767, /*max=*/32767, /*resolution=*/0);
+    }
+};
+
+TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
+    DisplayViewport viewport;
+    viewport.displayId = ui::LogicalDisplayId{1};
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(viewport));
+    mMapper = createInputMapper<JoystickInputMapper>(*mDeviceContext,
+                                                     mFakePolicy->getReaderConfiguration());
+
+    std::list<NotifyArgs> out;
+
+    // Send an axis event
+    out = process(EV_ABS, ABS_X, 100);
+    ASSERT_THAT(out, IsEmpty());
+    out = process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(out, ElementsAre(VariantWith<NotifyMotionArgs>(WithDisplayId(viewport.displayId))));
+
+    // Send another axis event
+    out = process(EV_ABS, ABS_Y, 100);
+    ASSERT_THAT(out, IsEmpty());
+    out = process(EV_SYN, SYN_REPORT, 0);
+    ASSERT_THAT(out, ElementsAre(VariantWith<NotifyMotionArgs>(WithDisplayId(viewport.displayId))));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index b44529b..88c25d3 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -55,7 +55,6 @@
 
     void SetUp() override {
         InputMapperUnitTest::SetUp();
-        createDevice();
 
         // set key-codes expected in tests
         for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
@@ -66,100 +65,13 @@
         mFakePolicy = sp<FakeInputReaderPolicy>::make();
         EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get()));
 
+        ON_CALL((*mDevice), getSources).WillByDefault(Return(AINPUT_SOURCE_KEYBOARD));
+
         mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
-                                                         AINPUT_SOURCE_KEYBOARD,
-                                                         AINPUT_KEYBOARD_TYPE_ALPHABETIC);
-    }
-
-    void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
-        EXPECT_CALL(mMockInputReaderContext, fadePointer)
-                .Times(expectVisible ? 0 : keyCodes.size());
-        for (int32_t keyCode : keyCodes) {
-            process(EV_KEY, keyCode, 1);
-            process(EV_SYN, SYN_REPORT, 0);
-            process(EV_KEY, keyCode, 0);
-            process(EV_SYN, SYN_REPORT, 0);
-        }
-    }
-
-    void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
-                                     const bool expectPrevent) {
-        EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).Times(keyCodes.size());
-        if (expectPrevent) {
-            EXPECT_CALL(mMockInputReaderContext, setPreventingTouchpadTaps(true))
-                    .Times(keyCodes.size());
-        }
-        for (int32_t keyCode : keyCodes) {
-            process(EV_KEY, keyCode, 1);
-            process(EV_SYN, SYN_REPORT, 0);
-            process(EV_KEY, keyCode, 0);
-            process(EV_SYN, SYN_REPORT, 0);
-        }
+                                                         AINPUT_SOURCE_KEYBOARD);
     }
 };
 
-/**
- * Pointer visibility should remain unaffected if there is no active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDoesNotHidePointer) {
-    testPointerVisibilityForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectVisible= */ true);
-}
-
-/**
- * Pointer should hide if there is a active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer should still hide if touchpad taps are already disabled
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
-    testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
- * active
- */
-TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDoesNotHidePointer) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    std::vector<int32_t> metaKeys{KEY_LEFTALT,   KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
-                                  KEY_FN,        KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
-                                  KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK,   KEY_SCROLLLOCK};
-    testPointerVisibilityForKeys(metaKeys, /* expectVisible= */ true);
-}
-
-/**
- * Touchpad tap should not be disabled if there is no active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDontDisableTouchpadTap) {
-    testTouchpadTapStateForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectPrevent= */ false);
-}
-
-/**
- * Touchpad tap should be disabled if there is a active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionDisableTouchpadTap) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    testTouchpadTapStateForKeys({KEY_0, KEY_A}, /* expectPrevent= */ true);
-}
-
-/**
- * Touchpad tap should not be disabled by meta keys even if Input Method Connection is active
- */
-TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDontDisableTouchpadTap) {
-    mFakePolicy->setIsInputMethodConnectionActive(true);
-    std::vector<int32_t> metaKeys{KEY_LEFTALT,   KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
-                                  KEY_FN,        KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
-                                  KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK,   KEY_SCROLLLOCK};
-    testTouchpadTapStateForKeys(metaKeys, /* expectPrevent= */ false);
-}
-
 TEST_F(KeyboardInputMapperUnitTest, KeyPressTimestampRecorded) {
     nsecs_t when = ARBITRARY_TIME;
     std::vector<int32_t> keyCodes{KEY_0, KEY_A, KEY_LEFTCTRL, KEY_RIGHTALT, KEY_LEFTSHIFT};
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 6606de8..0f92833 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -20,7 +20,6 @@
 #include <android-base/properties.h>
 #include <binder/Binder.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <inttypes.h>
 #include <linux/input.h>
 #include <log/log.h>
@@ -44,7 +43,7 @@
     identifier.product = productId;
     auto info = InputDeviceInfo();
     info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device",
-                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     return info;
 }
 
@@ -62,12 +61,12 @@
 
 InputEventTimeline getTestTimeline() {
     InputEventTimeline t(
-            /*isDown=*/true,
             /*eventTime=*/2,
             /*readTime=*/3,
             /*vendorId=*/0,
             /*productId=*/0,
-            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType=*/InputEventActionType::UNKNOWN_INPUT_EVENT);
     ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8);
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
     graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
@@ -117,9 +116,10 @@
 void LatencyTrackerTest::triggerEventReporting(nsecs_t lastEventTime) {
     const nsecs_t triggerEventTime =
             lastEventTime + std::chrono::nanoseconds(ANR_TIMEOUT).count() + 1;
-    mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/true, triggerEventTime,
+    mTracker->trackListener(/*inputEventId=*/1, triggerEventTime,
                             /*readTime=*/3, DEVICE_ID,
-                            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+                            /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+                            AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
 }
 
 void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
@@ -168,12 +168,15 @@
  * any additional ConnectionTimeline's.
  */
 TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
-    mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2,
-                            /*readTime=*/3, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(/*inputEventId=*/1, /*eventTime=*/2,
+                            /*readTime=*/3, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN},
+                            AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
     triggerEventReporting(/*eventTime=*/2);
-    assertReceivedTimeline(InputEventTimeline{/*isDown=*/false, /*eventTime=*/2,
-                                              /*readTime=*/3, /*vendorId=*/0, /*productID=*/0,
-                                              /*sources=*/{InputDeviceUsageSource::UNKNOWN}});
+    assertReceivedTimeline(
+            InputEventTimeline{/*eventTime=*/2,
+                               /*readTime=*/3, /*vendorId=*/0, /*productID=*/0,
+                               /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+                               /*inputEventActionType=*/InputEventActionType::UNKNOWN_INPUT_EVENT});
 }
 
 /**
@@ -204,8 +207,9 @@
 
     const auto& [connectionToken, expectedCT] = *expected.connectionTimelines.begin();
 
-    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
-                            DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId, expected.eventTime, expected.readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
     mTracker->trackFinishedEvent(inputEventId, connectionToken, expectedCT.deliveryTime,
                                  expectedCT.consumeTime, expectedCT.finishTime);
     mTracker->trackGraphicsLatency(inputEventId, connectionToken, expectedCT.graphicsTimeline);
@@ -221,14 +225,15 @@
 TEST_F(LatencyTrackerTest, WhenDuplicateEventsAreReported_DoesNotCrash) {
     constexpr nsecs_t inputEventId = 1;
     constexpr nsecs_t readTime = 3; // does not matter for this test
-    constexpr bool isDown = true;   // does not matter for this test
 
     // In the following 2 calls to trackListener, the inputEventId's are the same, but event times
     // are different.
-    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/1, readTime, DEVICE_ID,
-                            {InputDeviceUsageSource::UNKNOWN});
-    mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime, DEVICE_ID,
-                            {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId, /*eventTime=*/1, readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
+    mTracker->trackListener(inputEventId, /*eventTime=*/2, readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
 
     triggerEventReporting(/*eventTime=*/2);
     // Since we sent duplicate input events, the tracker should just delete all of them, because it
@@ -239,12 +244,12 @@
 TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
     constexpr int32_t inputEventId1 = 1;
     InputEventTimeline timeline1(
-            /*isDown*/ true,
             /*eventTime*/ 2,
             /*readTime*/ 3,
             /*vendorId=*/0,
             /*productId=*/0,
-            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+            /*inputEventType=*/InputEventActionType::UNKNOWN_INPUT_EVENT);
     timeline1.connectionTimelines.emplace(connection1,
                                           ConnectionTimeline(/*deliveryTime*/ 6, /*consumeTime*/ 7,
                                                              /*finishTime*/ 8));
@@ -256,12 +261,12 @@
 
     constexpr int32_t inputEventId2 = 10;
     InputEventTimeline timeline2(
-            /*isDown=*/false,
             /*eventTime=*/20,
             /*readTime=*/30,
             /*vendorId=*/0,
             /*productId=*/0,
-            /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+            /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType=*/InputEventActionType::UNKNOWN_INPUT_EVENT);
     timeline2.connectionTimelines.emplace(connection2,
                                           ConnectionTimeline(/*deliveryTime=*/60,
                                                              /*consumeTime=*/70,
@@ -273,11 +278,13 @@
     connectionTimeline2.setGraphicsTimeline(std::move(graphicsTimeline2));
 
     // Start processing first event
-    mTracker->trackListener(inputEventId1, timeline1.isDown, timeline1.eventTime,
-                            timeline1.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId1, timeline1.eventTime, timeline1.readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
     // Start processing second event
-    mTracker->trackListener(inputEventId2, timeline2.isDown, timeline2.eventTime,
-                            timeline2.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId2, timeline2.eventTime, timeline2.readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
     mTracker->trackFinishedEvent(inputEventId1, connection1, connectionTimeline1.deliveryTime,
                                  connectionTimeline1.consumeTime, connectionTimeline1.finishTime);
 
@@ -302,12 +309,14 @@
     const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
 
     for (size_t i = 1; i <= 100; i++) {
-        mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime,
-                                timeline.readTime, /*deviceId=*/DEVICE_ID,
-                                /*sources=*/{InputDeviceUsageSource::UNKNOWN});
-        expectedTimelines.push_back(InputEventTimeline{timeline.isDown, timeline.eventTime,
-                                                       timeline.readTime, timeline.vendorId,
-                                                       timeline.productId, timeline.sources});
+        mTracker->trackListener(/*inputEventId=*/i, timeline.eventTime, timeline.readTime,
+                                /*deviceId=*/DEVICE_ID,
+                                /*sources=*/{InputDeviceUsageSource::UNKNOWN},
+                                AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
+        expectedTimelines.push_back(InputEventTimeline{timeline.eventTime, timeline.readTime,
+                                                       timeline.vendorId, timeline.productId,
+                                                       timeline.sources,
+                                                       timeline.inputEventActionType});
     }
     // Now, complete the first event that was sent.
     mTracker->trackFinishedEvent(/*inputEventId=*/1, token, expectedCT.deliveryTime,
@@ -333,12 +342,13 @@
                                  expectedCT.consumeTime, expectedCT.finishTime);
     mTracker->trackGraphicsLatency(inputEventId, connection1, expectedCT.graphicsTimeline);
 
-    mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
-                            DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+    mTracker->trackListener(inputEventId, expected.eventTime, expected.readTime, DEVICE_ID,
+                            {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
+                            InputEventType::MOTION);
     triggerEventReporting(expected.eventTime);
-    assertReceivedTimeline(InputEventTimeline{expected.isDown, expected.eventTime,
-                                              expected.readTime, expected.vendorId,
-                                              expected.productId, expected.sources});
+    assertReceivedTimeline(InputEventTimeline{expected.eventTime, expected.readTime,
+                                              expected.vendorId, expected.productId,
+                                              expected.sources, expected.inputEventActionType});
 }
 
 /**
@@ -349,22 +359,92 @@
 TEST_F(LatencyTrackerTest, TrackListenerCheck_DeviceInfoFieldsInputEventTimeline) {
     constexpr int32_t inputEventId = 1;
     InputEventTimeline timeline(
-            /*isDown*/ true, /*eventTime*/ 2, /*readTime*/ 3,
+            /*eventTime*/ 2, /*readTime*/ 3,
             /*vendorId=*/50, /*productId=*/60,
             /*sources=*/
-            {InputDeviceUsageSource::TOUCHSCREEN, InputDeviceUsageSource::STYLUS_DIRECT});
+            {InputDeviceUsageSource::TOUCHSCREEN, InputDeviceUsageSource::STYLUS_DIRECT},
+            /*inputEventActionType=*/InputEventActionType::UNKNOWN_INPUT_EVENT);
     InputDeviceInfo deviceInfo1 = generateTestDeviceInfo(
             /*vendorId=*/5, /*productId=*/6, /*deviceId=*/DEVICE_ID + 1);
     InputDeviceInfo deviceInfo2 = generateTestDeviceInfo(
             /*vendorId=*/50, /*productId=*/60, /*deviceId=*/DEVICE_ID);
 
     mTracker->setInputDevices({deviceInfo1, deviceInfo2});
-    mTracker->trackListener(inputEventId, timeline.isDown, timeline.eventTime, timeline.readTime,
-                            DEVICE_ID,
+    mTracker->trackListener(inputEventId, timeline.eventTime, timeline.readTime, DEVICE_ID,
                             {InputDeviceUsageSource::TOUCHSCREEN,
-                             InputDeviceUsageSource::STYLUS_DIRECT});
+                             InputDeviceUsageSource::STYLUS_DIRECT},
+                            AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
     triggerEventReporting(timeline.eventTime);
     assertReceivedTimeline(timeline);
 }
 
+/**
+ * Check that InputEventActionType is correctly assigned to InputEventTimeline in trackListener.
+ */
+TEST_F(LatencyTrackerTest, TrackListenerCheck_InputEventActionTypeFieldInputEventTimeline) {
+    constexpr int32_t inputEventId = 1;
+    // Create timelines for different event types (Motion, Key)
+    InputEventTimeline motionDownTimeline(
+            /*eventTime*/ 2, /*readTime*/ 3,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_DOWN);
+
+    InputEventTimeline motionMoveTimeline(
+            /*eventTime*/ 4, /*readTime*/ 5,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_MOVE);
+
+    InputEventTimeline motionUpTimeline(
+            /*eventTime*/ 6, /*readTime*/ 7,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_UP);
+
+    InputEventTimeline keyDownTimeline(
+            /*eventTime*/ 8, /*readTime*/ 9,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::KEY);
+
+    InputEventTimeline keyUpTimeline(
+            /*eventTime*/ 10, /*readTime*/ 11,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::KEY);
+
+    InputEventTimeline unknownTimeline(
+            /*eventTime*/ 12, /*readTime*/ 13,
+            /*vendorId*/ 0, /*productId*/ 0,
+            /*sources*/ {InputDeviceUsageSource::UNKNOWN},
+            /*inputEventActionType*/ InputEventActionType::UNKNOWN_INPUT_EVENT);
+
+    mTracker->trackListener(inputEventId, motionDownTimeline.eventTime, motionDownTimeline.readTime,
+                            DEVICE_ID, motionDownTimeline.sources, AMOTION_EVENT_ACTION_DOWN,
+                            InputEventType::MOTION);
+    mTracker->trackListener(inputEventId + 1, motionMoveTimeline.eventTime,
+                            motionMoveTimeline.readTime, DEVICE_ID, motionMoveTimeline.sources,
+                            AMOTION_EVENT_ACTION_MOVE, InputEventType::MOTION);
+    mTracker->trackListener(inputEventId + 2, motionUpTimeline.eventTime, motionUpTimeline.readTime,
+                            DEVICE_ID, motionUpTimeline.sources, AMOTION_EVENT_ACTION_UP,
+                            InputEventType::MOTION);
+    mTracker->trackListener(inputEventId + 3, keyDownTimeline.eventTime, keyDownTimeline.readTime,
+                            DEVICE_ID, keyDownTimeline.sources, AKEY_EVENT_ACTION_DOWN,
+                            InputEventType::KEY);
+    mTracker->trackListener(inputEventId + 4, keyUpTimeline.eventTime, keyUpTimeline.readTime,
+                            DEVICE_ID, keyUpTimeline.sources, AKEY_EVENT_ACTION_UP,
+                            InputEventType::KEY);
+    mTracker->trackListener(inputEventId + 5, unknownTimeline.eventTime, unknownTimeline.readTime,
+                            DEVICE_ID, unknownTimeline.sources, AMOTION_EVENT_ACTION_POINTER_DOWN,
+                            InputEventType::MOTION);
+
+    triggerEventReporting(unknownTimeline.eventTime);
+
+    std::vector<InputEventTimeline> expectedTimelines = {motionDownTimeline, motionMoveTimeline,
+                                                         motionUpTimeline,   keyDownTimeline,
+                                                         keyUpTimeline,      unknownTimeline};
+    assertReceivedTimelines(expectedTimelines);
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
index d726385..9a6b266 100644
--- a/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
+++ b/services/inputflinger/tests/MultiTouchInputMapper_test.cpp
@@ -35,7 +35,7 @@
 using testing::SetArgPointee;
 using testing::VariantWith;
 
-static constexpr int32_t DISPLAY_ID = 0;
+static constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 static constexpr int32_t DISPLAY_WIDTH = 480;
 static constexpr int32_t DISPLAY_HEIGHT = 800;
 static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
@@ -99,11 +99,8 @@
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
         // reset current slot at the beginning
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-                .WillRepeatedly([](int32_t, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
 
         // mark all slots not in use
         mockSlotValues({});
@@ -112,7 +109,6 @@
         mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                         /*isActive=*/true, "local:0", NO_PORT,
                                         ViewportType::INTERNAL);
-        createDevice();
         mMapper = createInputMapper<MultiTouchInputMapper>(*mDeviceContext,
                                                            mFakePolicy->getReaderConfiguration());
     }
@@ -211,11 +207,8 @@
     const auto pointerCoordsBeforeReset = std::get<NotifyMotionArgs>(args.back()).pointerCoords;
 
     // On buffer overflow mapper will be reset and MT slots data will be repopulated
-    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, _))
-            .WillRepeatedly([=](int32_t, int32_t, int32_t* outValue) {
-                *outValue = 1;
-                return OK;
-            });
+    EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+            .WillRepeatedly(Return(1));
 
     mockSlotValues(
             {{1, {Point{x1, y1}, FIRST_TRACKING_ID}}, {2, {Point{x2, y2}, SECOND_TRACKING_ID}}});
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
index 5e67506..9ddb8c1 100644
--- a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -23,10 +23,7 @@
 protected:
     static constexpr size_t SLOT_COUNT = 8;
 
-    void SetUp() override {
-        InputMapperUnitTest::SetUp();
-        createDevice();
-    }
+    void SetUp() override { InputMapperUnitTest::SetUp(); }
 
     MultiTouchMotionAccumulator mMotionAccumulator;
 
@@ -38,7 +35,7 @@
         event.type = type;
         event.code = code;
         event.value = value;
-        mMotionAccumulator.process(&event);
+        mMotionAccumulator.process(event);
     }
 };
 
diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp
index 1536756..2e5ecc3 100644
--- a/services/inputflinger/tests/NotifyArgs_test.cpp
+++ b/services/inputflinger/tests/NotifyArgs_test.cpp
@@ -36,7 +36,7 @@
     nsecs_t readTime = downTime++;
     int32_t deviceId = 7;
     uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
-    int32_t displayId = 42;
+    ui::LogicalDisplayId displayId = ui::LogicalDisplayId{42};
     uint32_t policyFlags = POLICY_FLAG_GESTURE;
     int32_t action = AMOTION_EVENT_ACTION_HOVER_MOVE;
     int32_t actionButton = AMOTION_EVENT_BUTTON_PRIMARY;
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index b9c685e..411c7ba 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -15,18 +15,22 @@
  */
 
 #include "../PointerChoreographer.h"
-
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <deque>
 #include <vector>
 
 #include "FakePointerController.h"
+#include "InterfaceMocks.h"
 #include "NotifyArgsBuilders.h"
 #include "TestEventMatchers.h"
 #include "TestInputListener.h"
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 using ControllerType = PointerControllerInterface::ControllerType;
 using testing::AllOf;
 
@@ -42,8 +46,9 @@
 
 constexpr int32_t DEVICE_ID = 3;
 constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
-constexpr int32_t DISPLAY_ID = 5;
-constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t THIRD_DEVICE_ID = SECOND_DEVICE_ID + 1;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId{5};
+constexpr ui::LogicalDisplayId ANOTHER_DISPLAY_ID = ui::LogicalDisplayId{10};
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
 constexpr auto DRAWING_TABLET_SOURCE = AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS;
@@ -59,7 +64,7 @@
                                       .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
 
 static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
-                                              int32_t associatedDisplayId) {
+                                              ui::LogicalDisplayId associatedDisplayId) {
     InputDeviceIdentifier identifier;
 
     auto info = InputDeviceInfo();
@@ -69,7 +74,7 @@
     return info;
 }
 
-static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) {
+static std::vector<DisplayViewport> createViewports(std::vector<ui::LogicalDisplayId> displayIds) {
     std::vector<DisplayViewport> viewports;
     for (auto displayId : displayIds) {
         DisplayViewport viewport;
@@ -85,10 +90,55 @@
 
 // --- PointerChoreographerTest ---
 
-class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
+class TestPointerChoreographer : public PointerChoreographer {
+public:
+    TestPointerChoreographer(InputListenerInterface& inputListener,
+                             PointerChoreographerPolicyInterface& policy,
+                             sp<gui::WindowInfosListener>& windowInfoListener,
+                             const std::vector<gui::WindowInfo>& mInitialWindowInfos);
+};
+
+TestPointerChoreographer::TestPointerChoreographer(
+        InputListenerInterface& inputListener, PointerChoreographerPolicyInterface& policy,
+        sp<gui::WindowInfosListener>& windowInfoListener,
+        const std::vector<gui::WindowInfo>& mInitialWindowInfos)
+      : PointerChoreographer(
+                inputListener, policy,
+                [&windowInfoListener,
+                 &mInitialWindowInfos](const sp<android::gui::WindowInfosListener>& listener) {
+                    windowInfoListener = listener;
+                    return mInitialWindowInfos;
+                },
+                [&windowInfoListener](const sp<android::gui::WindowInfosListener>& listener) {
+                    windowInfoListener = nullptr;
+                }) {}
+
+class PointerChoreographerTest : public testing::Test {
 protected:
     TestInputListener mTestListener;
-    PointerChoreographer mChoreographer{mTestListener, *this};
+    sp<gui::WindowInfosListener> mRegisteredWindowInfoListener;
+    std::vector<gui::WindowInfo> mInjectedInitialWindowInfos;
+    testing::NiceMock<MockPointerChoreographerPolicyInterface> mMockPolicy;
+    TestPointerChoreographer mChoreographer{mTestListener, mMockPolicy,
+                                            mRegisteredWindowInfoListener,
+                                            mInjectedInitialWindowInfos};
+
+    void SetUp() override {
+        // flag overrides
+        input_flags::hide_pointer_indicators_for_secure_windows(true);
+
+        ON_CALL(mMockPolicy, createPointerController).WillByDefault([this](ControllerType type) {
+            std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+            EXPECT_FALSE(pc->isPointerShown());
+            mCreatedControllers.emplace_back(type, pc);
+            return pc;
+        });
+
+        ON_CALL(mMockPolicy, notifyPointerDisplayIdChanged)
+                .WillByDefault([this](ui::LogicalDisplayId displayId, const FloatPoint& position) {
+                    mPointerDisplayIdNotified = displayId;
+                });
+    }
 
     std::shared_ptr<FakePointerController> assertPointerControllerCreated(
             ControllerType expectedType) {
@@ -120,35 +170,32 @@
                                         "reference to this PointerController";
     }
 
-    void assertPointerDisplayIdNotified(int32_t displayId) {
+    void assertPointerDisplayIdNotified(ui::LogicalDisplayId displayId) {
         ASSERT_EQ(displayId, mPointerDisplayIdNotified);
         mPointerDisplayIdNotified.reset();
     }
 
     void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); }
 
+    void assertWindowInfosListenerRegistered() {
+        ASSERT_NE(nullptr, mRegisteredWindowInfoListener)
+                << "WindowInfosListener was not registered";
+    }
+
+    void assertWindowInfosListenerNotRegistered() {
+        ASSERT_EQ(nullptr, mRegisteredWindowInfoListener)
+                << "WindowInfosListener was not unregistered";
+    }
+
 private:
     std::deque<std::pair<ControllerType, std::shared_ptr<FakePointerController>>>
             mCreatedControllers;
-    std::optional<int32_t> mPointerDisplayIdNotified;
-
-    std::shared_ptr<PointerControllerInterface> createPointerController(
-            ControllerType type) override {
-        std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
-        EXPECT_FALSE(pc->isPointerShown());
-        mCreatedControllers.emplace_back(type, pc);
-        return pc;
-    }
-
-    void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override {
-        mPointerDisplayIdNotified = displayId;
-    }
+    std::optional<ui::LogicalDisplayId> mPointerDisplayIdNotified;
 };
 
 TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
     const std::vector<NotifyArgs>
             allArgs{NotifyInputDevicesChangedArgs{},
-                    NotifyConfigurationChangedArgs{},
                     KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(),
                     MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                             .pointer(FIRST_TOUCH_POINTER)
@@ -166,9 +213,6 @@
                                    [&](const NotifyInputDevicesChangedArgs& args) {
                                        mTestListener.assertNotifyInputDevicesChangedWasCalled();
                                    },
-                                   [&](const NotifyConfigurationChangedArgs& args) {
-                                       mTestListener.assertNotifyConfigurationChangedWasCalled();
-                                   },
                                    [&](const NotifyKeyArgs& args) {
                                        mTestListener.assertNotifyKeyWasCalled();
                                    },
@@ -197,13 +241,17 @@
 
 TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) {
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 }
 
 TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) {
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
 
     // Remove the mouse.
@@ -214,7 +262,8 @@
 TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) {
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerNotCreated();
 }
 
@@ -248,7 +297,9 @@
     // For a mouse event without a target display, default viewport should be set for
     // the PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertViewportSet(DISPLAY_ID);
     ASSERT_TRUE(pc->isPointerShown());
@@ -260,7 +311,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     firstDisplayPc->assertViewportSet(DISPLAY_ID);
     ASSERT_TRUE(firstDisplayPc->isPointerShown());
@@ -279,7 +332,9 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 
     assertPointerDisplayIdNotified(DISPLAY_ID);
@@ -288,7 +343,9 @@
 TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplayIdChanged) {
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotNotified();
 
@@ -300,12 +357,14 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
-    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID);
     assertPointerControllerRemoved(pc);
 }
 
@@ -316,7 +375,9 @@
     // Set one viewport as a default mouse display ID.
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
@@ -332,7 +393,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -344,7 +407,7 @@
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(MOUSE_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that the PointerController updated the position and the pointer is shown.
@@ -356,6 +419,40 @@
             AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
 }
 
+TEST_F(PointerChoreographerTest, AbsoluteMouseMovesPointerAndReturnsNewArgs) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+    // Set initial position of the PointerController.
+    pc->setPosition(100, 200);
+    const auto absoluteMousePointer = PointerBuilder(/*id=*/0, ToolType::MOUSE)
+                                              .axis(AMOTION_EVENT_AXIS_X, 110)
+                                              .axis(AMOTION_EVENT_AXIS_Y, 220);
+
+    // Make NotifyMotionArgs and notify Choreographer.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                    .pointer(absoluteMousePointer)
+                    .deviceId(DEVICE_ID)
+                    .displayId(ui::LogicalDisplayId::INVALID)
+                    .build());
+
+    // Check that the PointerController updated the position and the pointer is shown.
+    pc->assertPosition(110, 220);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Check that x-y coordinates, displayId and cursor position are correctly updated.
+    mTestListener.assertNotifyMotionWasCalled(
+            AllOf(WithCoords(110, 220), WithRelativeMotion(10, 20), WithDisplayId(DISPLAY_ID),
+                  WithCursorPosition(110, 220)));
+}
+
 TEST_F(PointerChoreographerTest,
        AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
     // Add two displays and set one to default.
@@ -365,7 +462,7 @@
     // Add two devices, one unassociated and the other associated with non-default mouse display.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
     auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
@@ -401,7 +498,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -411,10 +510,12 @@
     // Assume that pointer capture is enabled.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/1,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE,
+                                     ui::LogicalDisplayId::INVALID)}});
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
 
     // Notify motion as if pointer capture is enabled.
     mChoreographer.notifyMotion(
@@ -425,7 +526,7 @@
                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
                                      .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that there's no update on the PointerController.
@@ -434,7 +535,8 @@
 
     // Check x-y coordinates, displayId and cursor position are not changed.
     mTestListener.assertNotifyMotionWasCalled(
-            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+            AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20),
+                  WithDisplayId(ui::LogicalDisplayId::INVALID),
                   WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                      AMOTION_EVENT_INVALID_CURSOR_POSITION)));
 }
@@ -443,7 +545,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
     ASSERT_TRUE(pc->isPointerShown());
@@ -451,7 +555,8 @@
     // Enable pointer capture and check if the PointerController hid the pointer.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
     ASSERT_FALSE(pc->isPointerShown());
 }
 
@@ -461,7 +566,9 @@
 
     // A mouse is connected, and the pointer is shown.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
 
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_TRUE(pc->isPointerShown());
@@ -471,15 +578,17 @@
     // Add a second mouse is added, the pointer is shown again.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     ASSERT_TRUE(pc->isPointerShown());
 
     // One of the mice is removed, and it does not cause the mouse pointer to fade, because
     // we have one more mouse connected.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+             {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerNotRemoved(pc);
     ASSERT_TRUE(pc->isPointerShown());
 
@@ -492,7 +601,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
 
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_TRUE(pc->isPointerShown());
@@ -502,8 +613,9 @@
     // Adding a touchscreen device does not unfade the mouse pointer.
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
-              generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID,
+                                     AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
                                      DISPLAY_ID)}});
 
     ASSERT_FALSE(pc->isPointerShown());
@@ -514,6 +626,75 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, DisabledMouseConnected) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+    // Disable this mouse device.
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Disabled mouse device should not create PointerController
+    assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, MouseDeviceDisableLater) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo mouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we disable this mouse device
+    mouseDeviceInfo.setEnabled(false);
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {mouseDeviceInfo}});
+
+    // Because the mouse device disabled, the PointerController should be removed.
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleEnabledAndDisabledMiceConnectionAndRemoval) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+    InputDeviceInfo disabledMouseDeviceInfo =
+            generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID);
+    disabledMouseDeviceInfo.setEnabled(false);
+
+    InputDeviceInfo enabledMouseDeviceInfo =
+            generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                   ui::LogicalDisplayId::INVALID);
+
+    InputDeviceInfo anotherEnabledMouseDeviceInfo =
+            generateTestDeviceInfo(THIRD_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                   ui::LogicalDisplayId::INVALID);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {disabledMouseDeviceInfo, enabledMouseDeviceInfo, anotherEnabledMouseDeviceInfo}});
+
+    // Mouse should show, because we have two enabled mice device.
+    auto pc = assertPointerControllerCreated(PointerControllerInterface::ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Now we remove one of enabled mice device.
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {disabledMouseDeviceInfo, enabledMouseDeviceInfo}});
+
+    // Because we still have an enabled mouse device, pointer should still show.
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // We finally remove last enabled mouse device.
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {disabledMouseDeviceInfo}});
+
+    // The PointerController should be removed, because there is no enabled mouse device.
+    assertPointerControllerRemoved(pc);
+}
+
 TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
     // Disable show touches and add a touch device.
     mChoreographer.setShowTouchesEnabled(false);
@@ -645,15 +826,20 @@
     pc->assertSpotCount(DISPLAY_ID, 0);
 }
 
+/**
+ * In this test, we simulate the complete event of the stylus approaching and clicking on the
+ * screen, and then leaving the screen. We should ensure that spots are displayed correctly.
+ */
 TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) {
     mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.setStylusPointerIconEnabled(false);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
                                      DISPLAY_ID)}});
 
-    // Emit down event with stylus properties.
-    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+    // First, the stylus begin to approach the screen.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
                                                   AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
                                         .pointer(STYLUS_POINTER)
                                         .deviceId(DEVICE_ID)
@@ -661,6 +847,72 @@
                                         .build());
     auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
     pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 0);
+
+    // Now, use stylus touch the screen.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_UP,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 0);
+
+    // Then, the stylus start leave from the screen.
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 1);
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    pc->assertSpotCount(DISPLAY_ID, 0);
 }
 
 TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) {
@@ -726,6 +978,36 @@
     assertPointerControllerRemoved(pc);
 }
 
+/**
+ * When both "show touches" and "stylus hover icons" are enabled, if the app doesn't specify an
+ * icon for the hovering stylus, fall back to using the spot hover icon.
+ */
+TEST_F(PointerChoreographerTest, ShowTouchesOverridesUnspecifiedStylusIcon) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                     DISPLAY_ID)}});
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_SPOT_HOVER);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_ARROW, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_ARROW);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_SPOT_HOVER);
+}
+
 using StylusFixtureParam =
         std::tuple</*name*/ std::string_view, /*source*/ uint32_t, ControllerType>;
 
@@ -1052,7 +1334,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 }
 
@@ -1060,7 +1342,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
 
     // Remove the touchpad.
@@ -1101,7 +1383,9 @@
     // For a touchpad event without a target display, default viewport should be set for
     // the PointerController.
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertViewportSet(DISPLAY_ID);
 }
@@ -1114,7 +1398,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     firstDisplayPc->assertViewportSet(DISPLAY_ID);
 
@@ -1132,7 +1416,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
 
     assertPointerDisplayIdNotified(DISPLAY_ID);
@@ -1143,7 +1427,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotNotified();
 
@@ -1157,12 +1441,12 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
     mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
-    assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+    assertPointerDisplayIdNotified(ui::LogicalDisplayId::INVALID);
     assertPointerControllerRemoved(pc);
 }
 
@@ -1176,12 +1460,12 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
     assertPointerDisplayIdNotified(DISPLAY_ID);
 
-    // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
-    // before a touchpad event.
+    // Set another viewport as a default mouse display ID. ui::LogicalDisplayId::INVALID will be
+    // notified before a touchpad event.
     mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
     assertPointerControllerRemoved(firstDisplayPc);
 
@@ -1195,7 +1479,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1207,7 +1491,7 @@
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(TOUCHPAD_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
 
     // Check that the PointerController updated the position and the pointer is shown.
@@ -1225,7 +1509,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1239,7 +1523,7 @@
                     .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
@@ -1253,7 +1537,7 @@
                     .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -1270,7 +1554,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
@@ -1285,7 +1569,7 @@
                     .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10))
                     .classification(MotionClassification::MULTI_FINGER_SWIPE)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     mTestListener.assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
@@ -1304,7 +1588,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE),
+                                     ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
                                      ANOTHER_DISPLAY_ID)}});
     auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
@@ -1343,7 +1627,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
 
@@ -1353,13 +1637,14 @@
     // Assume that pointer capture is enabled.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
 
     // Notify motion as if pointer capture is enabled.
     mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
                                         .pointer(FIRST_TOUCH_POINTER)
                                         .deviceId(DEVICE_ID)
-                                        .displayId(ADISPLAY_ID_NONE)
+                                        .displayId(ui::LogicalDisplayId::INVALID)
                                         .build());
 
     // Check that there's no update on the PointerController.
@@ -1368,7 +1653,7 @@
 
     // Check x-y coordinates, displayId and cursor position are not changed.
     mTestListener.assertNotifyMotionWasCalled(
-            AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE),
+            AllOf(WithCoords(100, 200), WithDisplayId(ui::LogicalDisplayId::INVALID),
                   WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
                                      AMOTION_EVENT_INVALID_CURSOR_POSITION)));
 }
@@ -1379,7 +1664,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
     ASSERT_TRUE(pc->isPointerShown());
@@ -1387,7 +1672,8 @@
     // Enable pointer capture and check if the PointerController hid the pointer.
     mChoreographer.notifyPointerCaptureChanged(
             NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
-                                            PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+                                            PointerCaptureRequest(/*window=*/sp<BBinder>::make(),
+                                                                  /*seq=*/0)));
     ASSERT_FALSE(pc->isPointerShown());
 }
 
@@ -1396,7 +1682,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1410,7 +1698,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1425,7 +1715,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertPointerIconNotSet();
 
@@ -1440,7 +1732,9 @@
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
     pc->assertCustomPointerIconNotSet();
 
@@ -1463,7 +1757,7 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
     auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
@@ -1482,6 +1776,254 @@
     firstMousePc->assertPointerIconNotSet();
 }
 
+using SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam =
+        std::tuple<std::string_view /*name*/, uint32_t /*source*/, ControllerType, PointerBuilder,
+                   std::function<void(PointerChoreographer&)>, int32_t /*action*/>;
+
+class SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture
+      : public PointerChoreographerTest,
+        public ::testing::WithParamInterface<
+                SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam> {
+protected:
+    void initializePointerDevice(const PointerBuilder& pointerBuilder, const uint32_t source,
+                                 const std::function<void(PointerChoreographer&)> onControllerInit,
+                                 const int32_t action) {
+        mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+        // Add appropriate pointer device
+        mChoreographer.notifyInputDevicesChanged(
+                {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+        onControllerInit(mChoreographer);
+
+        // Emit input events to create PointerController
+        mChoreographer.notifyMotion(MotionArgsBuilder(action, source)
+                                            .pointer(pointerBuilder)
+                                            .deviceId(DEVICE_ID)
+                                            .displayId(DISPLAY_ID)
+                                            .build());
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+        PointerChoreographerTest, SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+        ::testing::Values(
+                std::make_tuple(
+                        "TouchSpots", AINPUT_SOURCE_TOUCHSCREEN, ControllerType::TOUCH,
+                        FIRST_TOUCH_POINTER,
+                        [](PointerChoreographer& pc) { pc.setShowTouchesEnabled(true); },
+                        AMOTION_EVENT_ACTION_DOWN),
+                std::make_tuple(
+                        "Mouse", AINPUT_SOURCE_MOUSE, ControllerType::MOUSE, MOUSE_POINTER,
+                        [](PointerChoreographer& pc) {}, AMOTION_EVENT_ACTION_DOWN),
+                std::make_tuple(
+                        "Stylus", AINPUT_SOURCE_STYLUS, ControllerType::STYLUS, STYLUS_POINTER,
+                        [](PointerChoreographer& pc) { pc.setStylusPointerIconEnabled(true); },
+                        AMOTION_EVENT_ACTION_HOVER_ENTER),
+                std::make_tuple(
+                        "DrawingTablet", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS,
+                        ControllerType::MOUSE, STYLUS_POINTER, [](PointerChoreographer& pc) {},
+                        AMOTION_EVENT_ACTION_HOVER_ENTER)),
+        [](const testing::TestParamInfo<
+                SkipPointerScreenshotForPrivacySensitiveDisplaysFixtureParam>& p) {
+            return std::string{std::get<0>(p.param)};
+        });
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       WindowInfosListenerIsOnlyRegisteredWhenRequired) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    assertWindowInfosListenerNotRegistered();
+
+    // Listener should registered when a pointer device is added
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+    assertWindowInfosListenerRegistered();
+
+    mChoreographer.notifyInputDevicesChanged({});
+    assertWindowInfosListenerNotRegistered();
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       InitialDisplayInfoIsPopulatedForListener) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    // listener should not be registered if there is no pointer device
+    assertWindowInfosListenerNotRegistered();
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    mInjectedInitialWindowInfos = {windowInfo};
+
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+    assertWindowInfosListenerRegistered();
+
+    // Pointer indicators should be hidden based on the initial display info
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // un-marking the privacy sensitive display should reset the state
+    windowInfo.inputConfig.clear();
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       SkipsPointerScreenshotForPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    // By default pointer indicators should not be hidden
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // marking a display privacy sensitive should set flag to hide pointer indicators on the
+    // display screenshot
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // un-marking the privacy sensitive display should reset the state
+    windowInfo.inputConfig.clear();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       DoesNotSkipPointerScreenshotForHiddenPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    // By default pointer indicators should not be hidden
+    auto pc = assertPointerControllerCreated(controllerType);
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_VISIBLE;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_P(SkipPointerScreenshotForPrivacySensitiveDisplaysTestFixture,
+       DoesNotUpdateControllerForUnchangedPrivacySensitiveWindows) {
+    const auto& [name, source, controllerType, pointerBuilder, onControllerInit, action] =
+            GetParam();
+    initializePointerDevice(pointerBuilder, source, onControllerInit, action);
+
+    auto pc = assertPointerControllerCreated(controllerType);
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    gui::WindowInfo windowInfo2 = windowInfo;
+    windowInfo2.inputConfig.clear();
+    pc->assertSkipScreenshotFlagChanged();
+
+    // controller should not be updated if there are no changes in privacy sensitive windows
+    mRegisteredWindowInfoListener->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                                        {{windowInfo, windowInfo2},
+                                                         {displayInfo},
+                                                         /*vsyncId=*/0,
+                                                         /*timestamp=*/0});
+    pc->assertSkipScreenshotFlagNotChanged();
+}
+
+TEST_F_WITH_FLAGS(
+        PointerChoreographerTest, HidesPointerScreenshotForExistingPrivacySensitiveWindows,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            hide_pointer_indicators_for_secure_windows))) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Add a first mouse device
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                        .pointer(MOUSE_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    assertWindowInfosListenerRegistered();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // Add a second touch device and controller
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+                    .pointer(FIRST_TOUCH_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Pointer indicators should be hidden for this controller by default
+    auto pc2 = assertPointerControllerCreated(ControllerType::TOUCH);
+    pc->assertIsSkipScreenshotFlagSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+
+    // un-marking the privacy sensitive display should reset the state
+    windowInfo.inputConfig.clear();
+    mRegisteredWindowInfoListener
+            ->onWindowInfosChanged(/*windowInfosUpdate=*/
+                                   {{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+
+    pc->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+    pc2->assertIsSkipScreenshotFlagNotSet(DISPLAY_ID);
+    pc2->assertIsSkipScreenshotFlagNotSet(ANOTHER_DISPLAY_ID);
+}
+
 TEST_P(StylusTestFixture, SetsPointerIconForStylus) {
     const auto& [name, source, controllerType] = GetParam();
 
@@ -1588,14 +2130,14 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
     mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
     mChoreographer.notifyMotion(
             MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
                     .pointer(MOUSE_POINTER)
                     .deviceId(DEVICE_ID)
-                    .displayId(ADISPLAY_ID_NONE)
+                    .displayId(ui::LogicalDisplayId::INVALID)
                     .build());
     auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, source)
@@ -1623,7 +2165,7 @@
     mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
-             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ui::LogicalDisplayId::INVALID),
               generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
     auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
@@ -1679,7 +2221,9 @@
     // Hide the pointer on the display, and then connect the mouse.
     mChoreographer.setPointerIconVisibility(DISPLAY_ID, false);
     mChoreographer.notifyInputDevicesChanged(
-            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
     auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, mousePc->getDisplayId());
 
@@ -1697,7 +2241,7 @@
     mChoreographer.notifyInputDevicesChanged(
             {/*id=*/0,
              {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
-                                     ADISPLAY_ID_NONE)}});
+                                     ui::LogicalDisplayId::INVALID)}});
     auto touchpadPc = assertPointerControllerCreated(ControllerType::MOUSE);
     ASSERT_EQ(DISPLAY_ID, touchpadPc->getDisplayId());
 
@@ -1736,4 +2280,362 @@
     ASSERT_FALSE(pc->isPointerShown());
 }
 
+TEST_F(PointerChoreographerTest, DrawingTabletCanReportMouseEvent) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    // There should be no controller created when a drawing tablet is connected
+    assertPointerControllerNotCreated();
+
+    // But if it ends up reporting a mouse event, then the mouse controller will be created
+    // dynamically.
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // The controller is removed when the drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MultipleDrawingTabletsReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // First drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotCreated();
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Second drawing tablet is added
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(SECOND_DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // First drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    assertPointerControllerNotRemoved(pc);
+
+    // Second drawing tablet is removed
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) {
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+    mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+    // Mouse and drawing tablet connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE,
+                                     ui::LogicalDisplayId::INVALID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    // Drawing tablet reports a mouse event
+    mChoreographer.notifyMotion(
+            MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, DRAWING_TABLET_SOURCE)
+                    .pointer(MOUSE_POINTER)
+                    .deviceId(DEVICE_ID)
+                    .displayId(DISPLAY_ID)
+                    .build());
+
+    // Remove the mouse device
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, DRAWING_TABLET_SOURCE,
+                                     ui::LogicalDisplayId::INVALID)}});
+
+    // The mouse controller should not be removed, because the drawing tablet has produced a
+    // mouse event, so we are treating it as a mouse too.
+    assertPointerControllerNotRemoved(pc);
+
+    mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+    assertPointerControllerRemoved(pc);
+}
+
+using PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixtureParam =
+        std::tuple<std::string_view /*name*/, uint32_t /*source*/>;
+
+class PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture
+      : public PointerChoreographerTest,
+        public testing::WithParamInterface<
+                PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixtureParam> {
+protected:
+    const std::unordered_map<int32_t, int32_t>
+            mMetaKeyStates{{AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON},
+                           {AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON},
+                           {AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON},
+                           {AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON},
+                           {AKEYCODE_SYM, AMETA_SYM_ON},
+                           {AKEYCODE_FUNCTION, AMETA_FUNCTION_ON},
+                           {AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON},
+                           {AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON},
+                           {AKEYCODE_META_LEFT, AMETA_META_LEFT_ON},
+                           {AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON},
+                           {AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON},
+                           {AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON},
+                           {AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON}};
+
+    void notifyKey(ui::LogicalDisplayId targetDisplay, int32_t keyCode,
+                   int32_t metaState = AMETA_NONE) {
+        if (metaState == AMETA_NONE && mMetaKeyStates.contains(keyCode)) {
+            // For simplicity, we always set the corresponding meta state when sending a meta
+            // keycode. This does not take into consideration when the meta state is updated in
+            // reality.
+            metaState = mMetaKeyStates.at(keyCode);
+        }
+        mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+                                         .displayId(targetDisplay)
+                                         .keyCode(keyCode)
+                                         .metaState(metaState)
+                                         .build());
+        mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+                                         .displayId(targetDisplay)
+                                         .keyCode(keyCode)
+                                         .metaState(metaState)
+                                         .build());
+    }
+
+    void metaKeyCombinationHidesPointer(FakePointerController& pc, int32_t keyCode,
+                                        int32_t metaKeyCode) {
+        ASSERT_TRUE(pc.isPointerShown());
+        notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+        ASSERT_FALSE(pc.isPointerShown());
+
+        unfadePointer();
+    }
+
+    void metaKeyCombinationDoesNotHidePointer(FakePointerController& pc, int32_t keyCode,
+                                              int32_t metaKeyCode) {
+        ASSERT_TRUE(pc.isPointerShown());
+        notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+        ASSERT_TRUE(pc.isPointerShown());
+    }
+
+    void unfadePointer() {
+        // unfade pointer by injecting mose hover event
+        mChoreographer.notifyMotion(
+                MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+                        .pointer(MOUSE_POINTER)
+                        .deviceId(DEVICE_ID)
+                        .displayId(DISPLAY_ID)
+                        .build());
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P(
+        PointerChoreographerTest, PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+        testing::Values(std::make_tuple("Mouse", AINPUT_SOURCE_MOUSE),
+                        std::make_tuple("Touchpad", AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD)),
+        [](const testing::TestParamInfo<
+                PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixtureParam>& p) {
+            return std::string{std::get<0>(p.param)};
+        });
+
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       KeystrokesWithoutImeConnectionDoesNotHidePointerOrDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(0);
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_CTRL_LEFT);
+
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       AlphanumericKeystrokesWithImeConnectionHidePointerAndDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(2);
+
+    notifyKey(DISPLAY_ID, AKEYCODE_0);
+    ASSERT_FALSE(pc->isPointerShown());
+
+    unfadePointer();
+
+    notifyKey(DISPLAY_ID, AKEYCODE_A);
+    ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       MetaKeystrokesDoNotHidePointerOrDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(SECOND_DEVICE_ID, source, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(0);
+
+    const std::vector<int32_t> metaKeyCodes{AKEYCODE_ALT_LEFT,   AKEYCODE_ALT_RIGHT,
+                                            AKEYCODE_SHIFT_LEFT, AKEYCODE_SHIFT_RIGHT,
+                                            AKEYCODE_SYM,        AKEYCODE_FUNCTION,
+                                            AKEYCODE_CTRL_LEFT,  AKEYCODE_CTRL_RIGHT,
+                                            AKEYCODE_META_LEFT,  AKEYCODE_META_RIGHT,
+                                            AKEYCODE_CAPS_LOCK,  AKEYCODE_NUM_LOCK,
+                                            AKEYCODE_SCROLL_LOCK};
+    for (int32_t keyCode : metaKeyCodes) {
+        notifyKey(ui::LogicalDisplayId::INVALID, keyCode);
+    }
+
+    ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture,
+       KeystrokesWithoutTargetHidePointerOnlyOnFocusedDisplayAndDisablesTouchpadTap) {
+    const auto& [_, source] = GetParam();
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+    mChoreographer.setFocusedDisplay(DISPLAY_ID);
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID),
+              generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+    auto pc1 = assertPointerControllerCreated(ControllerType::MOUSE);
+    auto pc2 = assertPointerControllerCreated(ControllerType::MOUSE);
+    ASSERT_TRUE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(2);
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+    ASSERT_FALSE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+    unfadePointer();
+
+    notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+    ASSERT_FALSE(pc1->isPointerShown());
+    ASSERT_TRUE(pc2->isPointerShown());
+}
+
+TEST_P(PointerVisibilityAndTouchpadTapStateOnKeyPressTestFixture, TestMetaKeyCombinations) {
+    const auto& [_, source] = GetParam();
+    mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+    // Mouse connected
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, source, DISPLAY_ID)}});
+    auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+    EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+    // meta key combinations that should hide pointer and disable touchpad taps
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(5);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_LEFT);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_RIGHT);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_CAPS_LOCK);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_0, AKEYCODE_NUM_LOCK);
+    metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SCROLL_LOCK);
+
+    // meta key combinations that should not hide pointer
+    EXPECT_CALL(mMockPolicy, notifyMouseCursorFadedOnTyping).Times(0);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_RIGHT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_RIGHT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_SYM);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_FUNCTION);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_LEFT);
+    metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT);
+}
+
+class PointerChoreographerWindowInfoListenerTest : public testing::Test {};
+
+TEST_F_WITH_FLAGS(
+        PointerChoreographerWindowInfoListenerTest,
+        doesNotCrashIfListenerCalledAfterPointerChoreographerDestroyed,
+        REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                            hide_pointer_indicators_for_secure_windows))) {
+    sp<android::gui::WindowInfosListener> registeredListener;
+    sp<android::gui::WindowInfosListener> localListenerCopy;
+    {
+        testing::NiceMock<MockPointerChoreographerPolicyInterface> mockPolicy;
+        EXPECT_CALL(mockPolicy, createPointerController(ControllerType::MOUSE))
+                .WillOnce(testing::Return(std::make_shared<FakePointerController>()));
+        TestInputListener testListener;
+        std::vector<gui::WindowInfo> injectedInitialWindowInfos;
+        TestPointerChoreographer testChoreographer{testListener, mockPolicy, registeredListener,
+                                                   injectedInitialWindowInfos};
+        testChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+        // Add mouse to create controller and listener
+        testChoreographer.notifyInputDevicesChanged(
+                {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+
+        ASSERT_NE(nullptr, registeredListener) << "WindowInfosListener was not registered";
+        localListenerCopy = registeredListener;
+    }
+    ASSERT_EQ(nullptr, registeredListener) << "WindowInfosListener was not unregistered";
+
+    gui::WindowInfo windowInfo;
+    windowInfo.displayId = DISPLAY_ID;
+    windowInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    gui::DisplayInfo displayInfo;
+    displayInfo.displayId = DISPLAY_ID;
+    localListenerCopy->onWindowInfosChanged(
+            /*windowInfosUpdate=*/{{windowInfo}, {displayInfo}, /*vsyncId=*/0, /*timestamp=*/0});
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 9818176..a36d526 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include "../PreferStylusOverTouchBlocker.h"
 
 namespace android {
@@ -64,8 +63,9 @@
     }
 
     // Define a valid motion event.
-    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source, /*displayId=*/0,
-                          POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0,
+    NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, deviceId, source,
+                          ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action,
+                          /* actionButton */ 0,
                           /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
@@ -439,7 +439,7 @@
     InputDeviceInfo stylusDevice;
     stylusDevice.initialize(STYLUS_DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1,
                             /*identifier=*/{}, "stylus device", /*external=*/false,
-                            /*hasMic=*/false, ADISPLAY_ID_NONE);
+                            /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     notifyInputDevicesChanged({stylusDevice});
     // The touchscreen device was removed, so we no longer remember anything about it. We should
     // again start blocking touch events from it.
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
new file mode 100644
index 0000000..6607bc7
--- /dev/null
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2024 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 "RotaryEncoderInputMapper.h"
+
+#include <list>
+#include <string>
+#include <tuple>
+#include <variant>
+
+#include <android-base/logging.h>
+#include <android_companion_virtualdevice_flags.h>
+#include <gtest/gtest.h>
+#include <input/DisplayViewport.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/Timers.h>
+
+#include "InputMapperTest.h"
+#include "InputReaderBase.h"
+#include "InterfaceMocks.h"
+#include "NotifyArgs.h"
+#include "TestEventMatchers.h"
+#include "ui/Rotation.h"
+
+#define TAG "RotaryEncoderInputMapper_test"
+
+namespace android {
+
+using testing::AllOf;
+using testing::Return;
+using testing::VariantWith;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
+constexpr ui::LogicalDisplayId SECONDARY_DISPLAY_ID = ui::LogicalDisplayId{DISPLAY_ID.val() + 1};
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+namespace {
+
+DisplayViewport createViewport() {
+    DisplayViewport v;
+    v.orientation = ui::Rotation::Rotation0;
+    v.logicalRight = DISPLAY_HEIGHT;
+    v.logicalBottom = DISPLAY_WIDTH;
+    v.physicalRight = DISPLAY_HEIGHT;
+    v.physicalBottom = DISPLAY_WIDTH;
+    v.deviceWidth = DISPLAY_HEIGHT;
+    v.deviceHeight = DISPLAY_WIDTH;
+    v.isActive = true;
+    return v;
+}
+
+DisplayViewport createPrimaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = DISPLAY_ID;
+    v.uniqueId = "local:1";
+    return v;
+}
+
+DisplayViewport createSecondaryViewport() {
+    DisplayViewport v = createViewport();
+    v.displayId = SECONDARY_DISPLAY_ID;
+    v.uniqueId = "local:2";
+    v.type = ViewportType::EXTERNAL;
+    return v;
+}
+
+} // namespace
+
+namespace vd_flags = android::companion::virtualdevice::flags;
+
+/**
+ * Unit tests for RotaryEncoderInputMapper.
+ */
+class RotaryEncoderInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override { SetUpWithBus(BUS_USB); }
+    void SetUpWithBus(int bus) override {
+        InputMapperUnitTest::SetUpWithBus(bus);
+
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL))
+                .WillRepeatedly(Return(true));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+        EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
+                .WillRepeatedly(Return(false));
+    }
+};
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
+    DisplayViewport primaryViewport = createPrimaryViewport();
+    DisplayViewport secondaryViewport = createSecondaryViewport();
+    mReaderConfiguration.setDisplayViewports({primaryViewport, secondaryViewport});
+
+    // Set up the secondary display as the associated viewport of the mapper.
+    EXPECT_CALL((*mDevice), getAssociatedViewport).WillRepeatedly(Return(secondaryViewport));
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    // Ensure input events are generated for the secondary display.
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(SECONDARY_DISPLAY_ID)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdNoAssociatedViewport) {
+    // Set up the default display.
+    mFakePolicy->clearViewports();
+    mFakePolicy->addDisplayViewport(createPrimaryViewport());
+
+    // Set up the mapper with no associated viewport.
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    // Ensure input events are generated without display ID
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithMotionAction(AMOTION_EVENT_ACTION_SCROLL),
+                              WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithDisplayId(ui::LogicalDisplayId::INVALID)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, ProcessRegularScroll) {
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(1.0f)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, ProcessHighResScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
+}
+
+TEST_F(RotaryEncoderInputMapperTest, HighResScrollIgnoresRegularScroll) {
+    vd_flags::high_resolution_scroll(true);
+    EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_WHEEL_HI_RES))
+            .WillRepeatedly(Return(true));
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL_HI_RES, 60);
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 1);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithSource(AINPUT_SOURCE_ROTARY_ENCODER),
+                              WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/SwitchInputMapper_test.cpp b/services/inputflinger/tests/SwitchInputMapper_test.cpp
new file mode 100644
index 0000000..ebbf10b
--- /dev/null
+++ b/services/inputflinger/tests/SwitchInputMapper_test.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 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 "SwitchInputMapper.h"
+
+#include <list>
+#include <variant>
+
+#include <NotifyArgs.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <linux/input-event-codes.h>
+
+#include "InputMapperTest.h"
+#include "TestConstants.h"
+
+namespace android {
+
+class SwitchInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        mMapper = createInputMapper<SwitchInputMapper>(*mDeviceContext,
+                                                       mFakePolicy->getReaderConfiguration());
+    }
+};
+
+TEST_F(SwitchInputMapperTest, GetSources) {
+    ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mMapper->getSources());
+}
+
+TEST_F(SwitchInputMapperTest, GetSwitchState) {
+    setSwitchState(1, {SW_LID});
+    ASSERT_EQ(1, mMapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+
+    setSwitchState(0, {SW_LID});
+    ASSERT_EQ(0, mMapper->getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
+}
+
+TEST_F(SwitchInputMapperTest, Process) {
+    std::list<NotifyArgs> out;
+    out = process(ARBITRARY_TIME, EV_SW, SW_LID, 1);
+    ASSERT_TRUE(out.empty());
+    out = process(ARBITRARY_TIME, EV_SW, SW_JACK_PHYSICAL_INSERT, 1);
+    ASSERT_TRUE(out.empty());
+    out = process(ARBITRARY_TIME, EV_SW, SW_HEADPHONE_INSERT, 0);
+    ASSERT_TRUE(out.empty());
+    out = process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(1u, out.size());
+    const NotifySwitchArgs& args = std::get<NotifySwitchArgs>(*out.begin());
+    ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+    ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT), args.switchValues);
+    ASSERT_EQ((1U << SW_LID) | (1U << SW_JACK_PHYSICAL_INSERT) | (1 << SW_HEADPHONE_INSERT),
+              args.switchMask);
+    ASSERT_EQ(uint32_t(0), args.policyFlags);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestConstants.h b/services/inputflinger/tests/TestConstants.h
index ad48b0f..d2337dd 100644
--- a/services/inputflinger/tests/TestConstants.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -24,6 +24,12 @@
 
 using std::chrono_literals::operator""ms;
 
+// Timeout for waiting for an input device to be added and processed
+static constexpr std::chrono::duration ADD_INPUT_DEVICE_TIMEOUT = 5000ms;
+
+// Timeout for asserting that an input device change did not occur
+static constexpr std::chrono::duration INPUT_DEVICES_DIDNT_CHANGE_TIMEOUT = 100ms;
+
 // Timeout for waiting for an expected event
 static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
 
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index 76170eb..6fa3365 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -145,7 +145,7 @@
 class WithDisplayIdMatcher {
 public:
     using is_gtest_matcher = void;
-    explicit WithDisplayIdMatcher(int32_t displayId) : mDisplayId(displayId) {}
+    explicit WithDisplayIdMatcher(ui::LogicalDisplayId displayId) : mDisplayId(displayId) {}
 
     bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
         return mDisplayId == args.displayId;
@@ -164,10 +164,10 @@
     void DescribeNegationTo(std::ostream* os) const { *os << "wrong display id"; }
 
 private:
-    const int32_t mDisplayId;
+    const ui::LogicalDisplayId mDisplayId;
 };
 
-inline WithDisplayIdMatcher WithDisplayId(int32_t displayId) {
+inline WithDisplayIdMatcher WithDisplayId(ui::LogicalDisplayId displayId) {
     return WithDisplayIdMatcher(displayId);
 }
 
@@ -615,7 +615,12 @@
     explicit WithPointerIdMatcher(size_t index, int32_t pointerId)
           : mIndex(index), mPointerId(pointerId) {}
 
-    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream* os) const {
+        if (mIndex >= args.pointerCoords.size()) {
+            *os << "Pointer index " << mIndex << " is out of bounds";
+            return false;
+        }
+
         return args.pointerProperties[mIndex].id == mPointerId;
     }
 
@@ -646,12 +651,51 @@
     return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
 }
 
-MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
-    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
-                     << ", " << argY << ")";
-    return argX == x && argY == y;
+/// Relative motion matcher
+class WithRelativeMotionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithRelativeMotionMatcher(size_t pointerIndex, float relX, float relY)
+          : mPointerIndex(pointerIndex), mRelX(relX), mRelY(relY) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        if (mPointerIndex >= event.pointerCoords.size()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        const PointerCoords& coords = event.pointerCoords[mPointerIndex];
+        bool matches = mRelX == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) &&
+                mRelY == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        if (!matches) {
+            *os << "expected relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+                << mPointerIndex << ", but got ("
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) << ", "
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y) << ")";
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+            << mPointerIndex;
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong relative motion"; }
+
+private:
+    const size_t mPointerIndex;
+    const float mRelX;
+    const float mRelY;
+};
+
+inline WithRelativeMotionMatcher WithRelativeMotion(float relX, float relY) {
+    return WithRelativeMotionMatcher(0, relX, relY);
+}
+
+inline WithRelativeMotionMatcher WithPointerRelativeMotion(size_t pointerIndex, float relX,
+                                                           float relY) {
+    return WithRelativeMotionMatcher(pointerIndex, relX, relY);
 }
 
 MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
@@ -720,6 +764,21 @@
     return argDistance == distance;
 }
 
+MATCHER_P(WithScroll, scroll, "InputEvent with specified scroll value") {
+    const auto argScroll = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_SCROLL);
+    *result_listener << "expected scroll value " << scroll << ", but got " << argScroll;
+    return argScroll == scroll;
+}
+
+MATCHER_P2(WithScroll, scrollX, scrollY, "InputEvent with specified scroll values") {
+    const auto argScrollX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_HSCROLL);
+    const auto argScrollY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_VSCROLL);
+    *result_listener << "expected scroll values " << scrollX << " scroll x " << scrollY
+                     << " scroll y, but got " << argScrollX << " scroll x " << argScrollY
+                     << " scroll y";
+    return argScrollX == scrollX && argScrollY == scrollY;
+}
+
 MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
     const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
     const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
@@ -743,10 +802,14 @@
     return argToolType == toolType;
 }
 
-MATCHER_P2(WithPointerToolType, pointer, toolType,
+MATCHER_P2(WithPointerToolType, pointerIndex, toolType,
            "InputEvent with specified tool type for pointer") {
-    const auto argToolType = arg.pointerProperties[pointer].toolType;
-    *result_listener << "expected pointer " << pointer << " to have tool type "
+    if (std::cmp_greater_equal(pointerIndex, arg.getPointerCount())) {
+        *result_listener << "Pointer index " << pointerIndex << " is out of bounds";
+        return false;
+    }
+    const auto argToolType = arg.pointerProperties[pointerIndex].toolType;
+    *result_listener << "expected pointer " << pointerIndex << " to have tool type "
                      << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
     return argToolType == toolType;
 }
diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 41e250f..369f9cc 100644
--- a/services/inputflinger/tests/TestInputListener.cpp
+++ b/services/inputflinger/tests/TestInputListener.cpp
@@ -37,19 +37,6 @@
                                                         "to have been called."));
 }
 
-void TestInputListener::assertNotifyConfigurationChangedWasCalled(
-        NotifyConfigurationChangedArgs* outEventArgs) {
-    ASSERT_NO_FATAL_FAILURE(
-            assertCalled<NotifyConfigurationChangedArgs>(outEventArgs,
-                                                         "Expected notifyConfigurationChanged() "
-                                                         "to have been called."));
-}
-
-void TestInputListener::assertNotifyConfigurationChangedWasNotCalled() {
-    ASSERT_NO_FATAL_FAILURE(assertNotCalled<NotifyConfigurationChangedArgs>(
-            "notifyConfigurationChanged() should not be called."));
-}
-
 void TestInputListener::assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs* outEventArgs) {
     ASSERT_NO_FATAL_FAILURE(
             assertCalled<
@@ -192,10 +179,6 @@
     addToQueue<NotifyInputDevicesChangedArgs>(args);
 }
 
-void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
-    addToQueue<NotifyConfigurationChangedArgs>(args);
-}
-
 void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
     addToQueue<NotifyDeviceResetArgs>(args);
 }
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 3c5e014..47eae4d 100644
--- a/services/inputflinger/tests/TestInputListener.h
+++ b/services/inputflinger/tests/TestInputListener.h
@@ -38,11 +38,6 @@
     void assertNotifyInputDevicesChangedWasCalled(
             NotifyInputDevicesChangedArgs* outEventArgs = nullptr);
 
-    void assertNotifyConfigurationChangedWasCalled(
-            NotifyConfigurationChangedArgs* outEventArgs = nullptr);
-
-    void assertNotifyConfigurationChangedWasNotCalled();
-
     void clearNotifyDeviceResetCalls();
 
     void assertNotifyDeviceResetWasCalled(const ::testing::Matcher<NotifyDeviceResetArgs>& matcher);
@@ -85,8 +80,6 @@
 
     virtual void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
 
-    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
-
     virtual void notifyDeviceReset(const NotifyDeviceResetArgs& args) override;
 
     virtual void notifyKey(const NotifyKeyArgs& args) override;
@@ -107,7 +100,6 @@
     const std::chrono::milliseconds mEventDidNotHappenTimeout;
 
     std::tuple<std::vector<NotifyInputDevicesChangedArgs>,   //
-               std::vector<NotifyConfigurationChangedArgs>,  //
                std::vector<NotifyDeviceResetArgs>,           //
                std::vector<NotifyKeyArgs>,                   //
                std::vector<NotifyMotionArgs>,                //
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
index a92dce5..ea69fff 100644
--- a/services/inputflinger/tests/TouchpadInputMapper_test.cpp
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -19,9 +19,7 @@
 #include <android-base/logging.h>
 #include <gtest/gtest.h>
 
-#include <com_android_input_flags.h>
 #include <thread>
-#include "FakePointerController.h"
 #include "InputMapperTest.h"
 #include "InterfaceMocks.h"
 #include "TestEventMatchers.h"
@@ -39,17 +37,15 @@
 constexpr auto HOVER_MOVE = AMOTION_EVENT_ACTION_HOVER_MOVE;
 constexpr auto HOVER_ENTER = AMOTION_EVENT_ACTION_HOVER_ENTER;
 constexpr auto HOVER_EXIT = AMOTION_EVENT_ACTION_HOVER_EXIT;
-constexpr int32_t DISPLAY_ID = 0;
+constexpr ui::LogicalDisplayId DISPLAY_ID = ui::LogicalDisplayId::DEFAULT;
 constexpr int32_t DISPLAY_WIDTH = 480;
 constexpr int32_t DISPLAY_HEIGHT = 800;
 constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
 
-namespace input_flags = com::android::input::flags;
-
 /**
  * Unit tests for TouchpadInputMapper.
  */
-class TouchpadInputMapperTestBase : public InputMapperUnitTest {
+class TouchpadInputMapperTest : public InputMapperUnitTest {
 protected:
     void SetUp() override {
         InputMapperUnitTest::SetUp();
@@ -107,28 +103,13 @@
         setupAxis(ABS_MT_DISTANCE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
         setupAxis(ABS_MT_TOOL_TYPE, /*valid=*/false, /*min=*/0, /*max=*/0, /*resolution=*/0);
 
-        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT, testing::_))
-                .WillRepeatedly([](int32_t eventHubId, int32_t, int32_t* outValue) {
-                    *outValue = 0;
-                    return OK;
-                });
+        EXPECT_CALL(mMockEventHub, getAbsoluteAxisValue(EVENTHUB_ID, ABS_MT_SLOT))
+                .WillRepeatedly(Return(0));
         EXPECT_CALL(mMockEventHub, getMtSlotValues(EVENTHUB_ID, testing::_, testing::_))
                 .WillRepeatedly([]() -> base::Result<std::vector<int32_t>> {
                     return base::ResultError("Axis not supported", NAME_NOT_FOUND);
                 });
-        createDevice();
-        mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration,
-                                                         isPointerChoreographerEnabled());
-    }
-
-    virtual bool isPointerChoreographerEnabled() { return false; }
-};
-
-class TouchpadInputMapperTest : public TouchpadInputMapperTestBase {
-protected:
-    void SetUp() override {
-        input_flags::enable_pointer_choreographer(false);
-        TouchpadInputMapperTestBase::SetUp();
+        mMapper = createInputMapper<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
     }
 };
 
@@ -139,66 +120,6 @@
  * but only after the button is released.
  */
 TEST_F(TouchpadInputMapperTest, HoverAndLeftButtonPress) {
-    std::list<NotifyArgs> args;
-
-    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
-    args += process(EV_KEY, BTN_TOUCH, 1);
-    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
-    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
-    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
-    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
-    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args, testing::IsEmpty());
-
-    // Without this sleep, the test fails.
-    // TODO(b/284133337): Figure out whether this can be removed
-    std::this_thread::sleep_for(std::chrono::milliseconds(20));
-
-    args += process(EV_KEY, BTN_LEFT, 1);
-    setScanCodeState(KeyState::DOWN, {BTN_LEFT});
-    args += process(EV_SYN, SYN_REPORT, 0);
-
-    args += process(EV_KEY, BTN_LEFT, 0);
-    setScanCodeState(KeyState::UP, {BTN_LEFT});
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args,
-                ElementsAre(VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_EXIT)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_ENTER))));
-
-    // Liftoff
-    args.clear();
-    args += process(EV_ABS, ABS_MT_PRESSURE, 0);
-    args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
-    args += process(EV_KEY, BTN_TOUCH, 0);
-    setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
-    args += process(EV_KEY, BTN_TOOL_FINGER, 0);
-    args += process(EV_SYN, SYN_REPORT, 0);
-    ASSERT_THAT(args, testing::IsEmpty());
-}
-
-class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase {
-protected:
-    void SetUp() override { TouchpadInputMapperTestBase::SetUp(); }
-
-    bool isPointerChoreographerEnabled() override { return true; }
-};
-
-// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
-//   logic can be removed.
-/**
- * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
- * generated when hovering stops. Currently, it is not.
- * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
- * but only after the button is released.
- */
-TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) {
     mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
     mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
                                     /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
@@ -251,4 +172,22 @@
     ASSERT_THAT(args, testing::IsEmpty());
 }
 
+TEST_F(TouchpadInputMapperTest, TouchpadHardwareState) {
+    mReaderConfiguration.shouldNotifyTouchpadHardwareState = true;
+    std::list<NotifyArgs> args =
+            mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+                                 InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+
+    args += process(EV_ABS, ABS_MT_TRACKING_ID, 1);
+    args += process(EV_KEY, BTN_TOUCH, 1);
+    setScanCodeState(KeyState::DOWN, {BTN_TOOL_FINGER});
+    args += process(EV_KEY, BTN_TOOL_FINGER, 1);
+    args += process(EV_ABS, ABS_MT_POSITION_X, 50);
+    args += process(EV_ABS, ABS_MT_POSITION_Y, 50);
+    args += process(EV_ABS, ABS_MT_PRESSURE, 1);
+    args += process(EV_SYN, SYN_REPORT, 0);
+
+    mFakePolicy->assertTouchpadHardwareStateNotified();
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
index 78f7291..bbb2fc8 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -18,7 +18,6 @@
 #include <android-base/silent_death_test.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-#include <gui/constants.h>
 #include <linux/input.h>
 #include <thread>
 #include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
@@ -89,7 +88,8 @@
 
     // Define a valid motion event.
     NotifyMotionArgs args(/*id=*/0, eventTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
-                          /*displayId=*/0, POLICY_FLAG_PASS_TO_USER, action, /*actionButton=*/0,
+                          ui::LogicalDisplayId::DEFAULT, POLICY_FLAG_PASS_TO_USER, action,
+                          /*actionButton=*/0,
                           /*flags=*/0, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE,
                           AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount, pointerProperties,
                           pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
@@ -104,7 +104,7 @@
 
     auto info = InputDeviceInfo();
     info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
-                    /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+                    /*isExternal=*/false, /*hasMic=*/false, ui::LogicalDisplayId::INVALID);
     info.addSource(AINPUT_SOURCE_TOUCHSCREEN);
     info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0,
                         /*fuzz=*/0, X_RESOLUTION);
@@ -414,27 +414,13 @@
 };
 
 /**
- * Create a basic configuration change and send it to input processor.
- * Expect that the event is received by the next input stage, unmodified.
- */
-TEST_F(UnwantedInteractionBlockerTest, ConfigurationChangedIsPassedToNextListener) {
-    // Create a basic configuration change and send to blocker
-    NotifyConfigurationChangedArgs args(/*sequenceNum=*/1, /*eventTime=*/2);
-
-    mBlocker->notifyConfigurationChanged(args);
-    NotifyConfigurationChangedArgs outArgs;
-    ASSERT_NO_FATAL_FAILURE(mTestListener.assertNotifyConfigurationChangedWasCalled(&outArgs));
-    ASSERT_EQ(args, outArgs);
-}
-
-/**
  * Keys are not handled in 'UnwantedInteractionBlocker' and should be passed
  * to next stage unmodified.
  */
 TEST_F(UnwantedInteractionBlockerTest, KeyIsPassedToNextListener) {
     // Create a basic key event and send to blocker
     NotifyKeyArgs args(/*sequenceNum=*/1, /*eventTime=*/2, /*readTime=*/21, /*deviceId=*/3,
-                       AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT, /*policyFlags=*/0,
+                       AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT, /*policyFlags=*/0,
                        AKEY_EVENT_ACTION_DOWN, /*flags=*/4, AKEYCODE_HOME, /*scanCode=*/5,
                        AMETA_NONE, /*downTime=*/6);
 
diff --git a/services/inputflinger/tests/VibratorInputMapper_test.cpp b/services/inputflinger/tests/VibratorInputMapper_test.cpp
new file mode 100644
index 0000000..6e3344c
--- /dev/null
+++ b/services/inputflinger/tests/VibratorInputMapper_test.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2024 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 "VibratorInputMapper.h"
+
+#include <chrono>
+#include <list>
+#include <variant>
+#include <vector>
+
+#include <EventHub.h>
+#include <NotifyArgs.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+
+#include "InputMapperTest.h"
+#include "VibrationElement.h"
+
+namespace android {
+
+class VibratorInputMapperTest : public InputMapperUnitTest {
+protected:
+    void SetUp() override {
+        InputMapperUnitTest::SetUp();
+        EXPECT_CALL(mMockEventHub, getDeviceClasses(EVENTHUB_ID))
+                .WillRepeatedly(testing::Return(InputDeviceClass::VIBRATOR));
+        EXPECT_CALL(mMockEventHub, getVibratorIds(EVENTHUB_ID))
+                .WillRepeatedly(testing::Return<std::vector<int32_t>>({0, 1}));
+        mMapper = createInputMapper<VibratorInputMapper>(*mDeviceContext,
+                                                         mFakePolicy->getReaderConfiguration());
+    }
+};
+
+TEST_F(VibratorInputMapperTest, GetSources) {
+    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mMapper->getSources());
+}
+
+TEST_F(VibratorInputMapperTest, GetVibratorIds) {
+    ASSERT_EQ(mMapper->getVibratorIds().size(), 2U);
+}
+
+TEST_F(VibratorInputMapperTest, Vibrate) {
+    constexpr uint8_t DEFAULT_AMPLITUDE = 192;
+    constexpr int32_t VIBRATION_TOKEN = 100;
+
+    VibrationElement pattern(2);
+    VibrationSequence sequence(2);
+    pattern.duration = std::chrono::milliseconds(200);
+    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2},
+                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
+    sequence.addElement(pattern);
+    pattern.duration = std::chrono::milliseconds(500);
+    pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4},
+                        {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
+    sequence.addElement(pattern);
+
+    std::vector<int64_t> timings = {0, 1};
+    std::vector<uint8_t> amplitudes = {DEFAULT_AMPLITUDE, DEFAULT_AMPLITUDE / 2};
+
+    ASSERT_FALSE(mMapper->isVibrating());
+    // Start vibrating
+    std::list<NotifyArgs> out = mMapper->vibrate(sequence, /*repeat=*/-1, VIBRATION_TOKEN);
+    ASSERT_TRUE(mMapper->isVibrating());
+    // Verify vibrator state listener was notified.
+    ASSERT_EQ(1u, out.size());
+    const NotifyVibratorStateArgs& vibrateArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
+    ASSERT_EQ(DEVICE_ID, vibrateArgs.deviceId);
+    ASSERT_TRUE(vibrateArgs.isOn);
+    // Stop vibrating
+    out = mMapper->cancelVibrate(VIBRATION_TOKEN);
+    ASSERT_FALSE(mMapper->isVibrating());
+    // Verify vibrator state listener was notified.
+    ASSERT_EQ(1u, out.size());
+    const NotifyVibratorStateArgs& cancelArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
+    ASSERT_EQ(DEVICE_ID, cancelArgs.deviceId);
+    ASSERT_FALSE(cancelArgs.isOn);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 81c3353..48e1954 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -178,7 +178,12 @@
     shared_libs: [
         "libinputreporter",
     ],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+    ],
     srcs: [
+        ":inputdispatcher_common_test_sources",
         "InputDispatcherFuzzer.cpp",
     ],
 }
diff --git a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
index af20a27..8361517 100644
--- a/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp
@@ -81,7 +81,7 @@
                             mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                InputReaderConfiguration::Change(0));
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    unused += mapper.process(&rawEvent);
+                    unused += mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
index 885820f..812969b 100644
--- a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -178,7 +178,7 @@
         pointerCoords.push_back(coords);
     }
 
-    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
+    const ui::LogicalDisplayId displayId{fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1)};
     const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);
 
     // Current time +- 5 seconds
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
index deb811d..46a6189 100644
--- a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
@@ -39,12 +39,6 @@
     while (fdp.remaining_bytes() > 0) {
         fdp.PickValueInArray<std::function<void()>>({
                 [&]() -> void {
-                    // SendToNextStage_NotifyConfigurationChangedArgs
-                    mClassifier->notifyConfigurationChanged(
-                            {/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
-                             /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>()});
-                },
-                [&]() -> void {
                     // SendToNextStage_NotifyKeyArgs
                     const nsecs_t eventTime =
                             fdp.ConsumeIntegralInRange<nsecs_t>(0,
@@ -54,7 +48,7 @@
                     mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
                                             eventTime, readTime,
                                             /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
-                                            AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
+                                            AINPUT_SOURCE_KEYBOARD, ui::LogicalDisplayId::DEFAULT,
                                             /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
                                             AKEY_EVENT_ACTION_DOWN,
                                             /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
index dc5a213..79a5ff6 100644
--- a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -18,7 +18,7 @@
 #include <fuzzer/FuzzedDataProvider.h>
 #include "../FakeApplicationHandle.h"
 #include "../FakeInputDispatcherPolicy.h"
-#include "../FakeWindowHandle.h"
+#include "../FakeWindows.h"
 #include "FuzzedInputStream.h"
 #include "dispatcher/InputDispatcher.h"
 #include "input/InputVerifier.h"
@@ -88,8 +88,9 @@
 
 } // namespace
 
-sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
-                                          int32_t displayId) {
+sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp,
+                                          std::unique_ptr<InputDispatcher>& dispatcher,
+                                          ui::LogicalDisplayId displayId) {
     static size_t windowNumber = 0;
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
@@ -100,10 +101,11 @@
     return window;
 }
 
-void randomizeWindows(
-        std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay,
-        FuzzedDataProvider& fdp, InputDispatcher& dispatcher) {
-    const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1);
+void randomizeWindows(std::unordered_map<ui::LogicalDisplayId, std::vector<sp<FakeWindowHandle>>>&
+                              windowsPerDisplay,
+                      FuzzedDataProvider& fdp, std::unique_ptr<InputDispatcher>& dispatcher) {
+    const ui::LogicalDisplayId displayId{
+            fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1)};
     std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId];
 
     fdp.PickValueInArray<std::function<void()>>({
@@ -142,12 +144,12 @@
     NotifyStreamProvider streamProvider(fdp);
 
     FakeInputDispatcherPolicy fakePolicy;
-    InputDispatcher dispatcher(fakePolicy);
-    dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+    auto dispatcher = std::make_unique<InputDispatcher>(fakePolicy);
+    dispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
     // Start InputDispatcher thread
-    dispatcher.start();
+    dispatcher->start();
 
-    std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
+    std::unordered_map<ui::LogicalDisplayId, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
 
     // Randomly invoke InputDispatcher api's until randomness is exhausted.
     while (fdp.remaining_bytes() > 0) {
@@ -155,7 +157,7 @@
                 [&]() -> void {
                     std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
                     if (motion) {
-                        dispatcher.notifyMotion(*motion);
+                        dispatcher->notifyMotion(*motion);
                     }
                 },
                 [&]() -> void {
@@ -169,7 +171,7 @@
                         }
                     }
 
-                    dispatcher.onWindowInfosChanged(
+                    dispatcher->onWindowInfosChanged(
                             {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
                 },
                 // Consume on all the windows
@@ -187,7 +189,7 @@
         })();
     }
 
-    dispatcher.stop();
+    dispatcher->stop();
 
     return 0;
 }
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 9223287..5442a65 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -55,8 +55,6 @@
 
     void monitor() { reader->monitor(); }
 
-    bool isInputDeviceEnabled(int32_t deviceId) { return reader->isInputDeviceEnabled(deviceId); }
-
     status_t start() { return reader->start(); }
 
     status_t stop() { return reader->stop(); }
@@ -119,7 +117,11 @@
         return reader->getSensors(deviceId);
     }
 
-    bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) {
+    std::optional<HardwareProperties> getTouchpadHardwareProperties(int32_t deviceId) {
+        return reader->getTouchpadHardwareProperties(deviceId);
+    }
+
+    bool canDispatchToDisplay(int32_t deviceId, ui::LogicalDisplayId displayId) {
         return reader->canDispatchToDisplay(deviceId, displayId);
     }
 
@@ -153,10 +155,6 @@
         return reader->getLightPlayerId(deviceId, lightId);
     }
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
-        reader->addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
-    }
-
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
         return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode);
     }
@@ -169,6 +167,10 @@
         reader->sysfsNodeChanged(sysfsNodePath);
     }
 
+    DeviceId getLastUsedInputDeviceId() override { return reader->getLastUsedInputDeviceId(); }
+
+    void notifyMouseCursorFadedOnTyping() override { reader->notifyMouseCursorFadedOnTyping(); }
+
 private:
     std::unique_ptr<InputReaderInterface> reader;
 };
@@ -204,7 +206,6 @@
                 },
                 [&]() -> void { reader->monitor(); },
                 [&]() -> void { reader->getInputDevices(); },
-                [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral<int32_t>()); },
                 [&]() -> void {
                     reader->getScanCodeState(fdp->ConsumeIntegral<int32_t>(),
                                              fdp->ConsumeIntegral<uint32_t>(),
@@ -241,7 +242,8 @@
                 },
                 [&]() -> void {
                     reader->canDispatchToDisplay(fdp->ConsumeIntegral<int32_t>(),
-                                                 fdp->ConsumeIntegral<int32_t>());
+                                                 ui::LogicalDisplayId{
+                                                         fdp->ConsumeIntegral<int32_t>()});
                 },
                 [&]() -> void {
                     reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral<int32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
index 922cbdf..9e02502 100644
--- a/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -50,11 +50,10 @@
     FuzzInputReaderContext context(eventHub, fdp);
     InputDevice device = getFuzzedInputDevice(*fdp, &context);
 
-    KeyboardInputMapper& mapper = getMapperForDevice<
-            ThreadSafeFuzzedDataProvider,
-            KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
-                                 /*source=*/fdp->ConsumeIntegral<uint32_t>(),
-                                 /*keyboardType=*/fdp->ConsumeIntegral<int32_t>());
+    KeyboardInputMapper& mapper =
+            getMapperForDevice<ThreadSafeFuzzedDataProvider,
+                               KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
+                                                    /*source=*/fdp->ConsumeIntegral<uint32_t>());
 
     // Loop through mapper operations until randomness is exhausted.
     while (fdp->remaining_bytes() > 0) {
@@ -80,7 +79,7 @@
                 },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
index 6daeaaf..695eb3c 100644
--- a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
@@ -18,6 +18,7 @@
 #include <linux/input.h>
 
 #include "../../InputDeviceMetricsSource.h"
+#include "../InputEventTimeline.h"
 #include "dispatcher/LatencyTracker.h"
 
 namespace android {
@@ -65,14 +66,15 @@
         fdp.PickValueInArray<std::function<void()>>({
                 [&]() -> void {
                     int32_t inputEventId = fdp.ConsumeIntegral<int32_t>();
-                    int32_t isDown = fdp.ConsumeBool();
                     nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
                     nsecs_t readTime = fdp.ConsumeIntegral<nsecs_t>();
                     const DeviceId deviceId = fdp.ConsumeIntegral<int32_t>();
                     std::set<InputDeviceUsageSource> sources = {
                             fdp.ConsumeEnum<InputDeviceUsageSource>()};
-                    tracker.trackListener(inputEventId, isDown, eventTime, readTime, deviceId,
-                                          sources);
+                    const int32_t inputEventActionType = fdp.ConsumeIntegral<int32_t>();
+                    const InputEventType inputEventType = fdp.ConsumeEnum<InputEventType>();
+                    tracker.trackListener(inputEventId, eventTime, readTime, deviceId, sources,
+                                          inputEventActionType, inputEventType);
                 },
                 [&]() -> void {
                     int32_t inputEventId = fdp.ConsumeIntegral<int32_t>();
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 7898126..fa8270a 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -17,6 +17,7 @@
 
 #include <map>
 #include <memory>
+#include <optional>
 
 #include <EventHub.h>
 #include <InputDevice.h>
@@ -31,8 +32,7 @@
                                   EV_MSC,
                                   EV_REL,
                                   android::EventHubInterface::DEVICE_ADDED,
-                                  android::EventHubInterface::DEVICE_REMOVED,
-                                  android::EventHubInterface::FINISHED_DEVICE_SCAN};
+                                  android::EventHubInterface::DEVICE_REMOVED};
 
 constexpr size_t kValidCodes[] = {
         SYN_REPORT,
@@ -119,16 +119,25 @@
     void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
         mAxes[deviceId][axis] = axisInfo;
     }
-    status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
-                                 RawAbsoluteAxisInfo* outAxisInfo) const override {
+    std::optional<RawAbsoluteAxisInfo> getAbsoluteAxisInfo(int32_t deviceId,
+                                                           int axis) const override {
         if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
             const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
             if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
-                *outAxisInfo = axisInfoIt->second;
-                return OK;
+                return axisInfoIt->second;
             }
         }
-        return mFdp->ConsumeIntegral<status_t>();
+        if (mFdp->ConsumeBool()) {
+            return std::optional<RawAbsoluteAxisInfo>({
+                    .minValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .maxValue = mFdp->ConsumeIntegral<int32_t>(),
+                    .flat = mFdp->ConsumeIntegral<int32_t>(),
+                    .fuzz = mFdp->ConsumeIntegral<int32_t>(),
+                    .resolution = mFdp->ConsumeIntegral<int32_t>(),
+            });
+        } else {
+            return std::nullopt;
+        }
     }
     bool hasRelativeAxis(int32_t deviceId, int axis) const override { return mFdp->ConsumeBool(); }
     bool hasInputProperty(int32_t deviceId, int property) const override {
@@ -193,13 +202,17 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override {}
+    void setKeyRemapping(int32_t deviceId,
+                         const std::map<int32_t, int32_t>& keyRemapping) const override {}
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
-                                  int32_t* outValue) const override {
-        return mFdp->ConsumeIntegral<status_t>();
+    std::optional<int32_t> getAbsoluteAxisValue(int32_t deviceId, int32_t axis) const override {
+        if (mFdp->ConsumeBool()) {
+            return mFdp->ConsumeIntegral<int32_t>();
+        } else {
+            return std::nullopt;
+        }
     }
     base::Result<std::vector<int32_t>> getMtSlotValues(int32_t deviceId, int32_t axis,
                                                        size_t slotCount) const override {
@@ -258,57 +271,20 @@
     void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
 };
 
-class FuzzPointerController : public PointerControllerInterface {
-    std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
-
-public:
-    FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
-    ~FuzzPointerController() {}
-    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 setPosition(float x, float y) 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 {}
-    void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
-                  BitSet32 spotIdBits, int32_t displayId) override {}
-    void clearSpots() override {}
-    int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
-    void setDisplayViewport(const DisplayViewport& displayViewport) override {}
-    void updatePointerIcon(PointerIconStyle iconId) override {}
-    void setCustomPointerIcon(const SpriteIcon& icon) override {}
-    std::string dump() override { return ""; }
-};
-
 class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
     TouchAffineTransformation mTransform;
-    std::shared_ptr<FuzzPointerController> mPointerController;
     std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
 
 protected:
     ~FuzzInputReaderPolicy() {}
 
 public:
-    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
-        mPointerController = std::make_shared<FuzzPointerController>(mFdp);
-    }
+    FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
-    std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override {
-        return mPointerController;
-    }
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
+    void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
+                                     int32_t deviceId) override {}
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> layoutInfo) override {
@@ -325,7 +301,7 @@
     void notifyStylusGestureStarted(int32_t, nsecs_t) {}
     bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); }
     std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
-            int32_t associatedDisplayId) override {
+            ui::LogicalDisplayId associatedDisplayId) override {
         return {};
     }
 };
@@ -333,7 +309,6 @@
 class FuzzInputListener : public virtual InputListenerInterface {
 public:
     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 {}
@@ -359,10 +334,6 @@
     bool shouldDropVirtualKey(nsecs_t now, int32_t keyCode, int32_t scanCode) override {
         return mFdp->ConsumeBool();
     }
-    void fadePointer() override {}
-    std::shared_ptr<PointerControllerInterface> getPointerController(int32_t deviceId) override {
-        return mPolicy->obtainPointerController(0);
-    }
     void requestTimeoutAtTime(nsecs_t when) override {}
     int32_t bumpGeneration() override { return mFdp->ConsumeIntegral<int32_t>(); }
     void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override {}
@@ -377,14 +348,16 @@
     int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
     void notifyStylusGestureStarted(int32_t, nsecs_t) {}
 
-    void setPreventingTouchpadTaps(bool prevent) {}
-    bool isPreventingTouchpadTaps() { return mFdp->ConsumeBool(); };
+    void setPreventingTouchpadTaps(bool prevent) override {}
+    bool isPreventingTouchpadTaps() override { return mFdp->ConsumeBool(); };
 
     void setLastKeyDownTimestamp(nsecs_t when) { mLastKeyDownTimestamp = when; };
     nsecs_t getLastKeyDownTimestamp() { return mLastKeyDownTimestamp; };
+    KeyboardClassifier& getKeyboardClassifier() override { return *mClassifier; }
 
 private:
     nsecs_t mLastKeyDownTimestamp;
+    std::unique_ptr<KeyboardClassifier> mClassifier = std::make_unique<KeyboardClassifier>();
 };
 
 template <class Fdp>
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
index d3f6690..f29577d 100644
--- a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -100,7 +100,7 @@
                 },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
index ac2030a..a42d447 100644
--- a/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -44,7 +44,7 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     RawEvent rawEvent = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+                    std::list<NotifyArgs> unused = mapper.process(rawEvent);
                 },
                 [&]() -> void {
                     mapper.getSwitchState(fdp->ConsumeIntegral<uint32_t>(),
diff --git a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
index c2bf275..ebbb311 100644
--- a/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -34,7 +34,6 @@
     if (fdp.ConsumeBool()) {
         eventHub.setAbsoluteAxisInfo(id, axis,
                                      RawAbsoluteAxisInfo{
-                                             .valid = fdp.ConsumeBool(),
                                              .minValue = fdp.ConsumeIntegral<int32_t>(),
                                              .maxValue = fdp.ConsumeIntegral<int32_t>(),
                                              .flat = fdp.ConsumeIntegral<int32_t>(),
@@ -125,6 +124,9 @@
     config.touchpadTapToClickEnabled = fdp.ConsumeBool();
     config.touchpadTapDraggingEnabled = fdp.ConsumeBool();
     config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
+
+    config.pointerCaptureRequest.window = fdp.ConsumeBool() ? sp<BBinder>::make() : nullptr;
+    config.pointerCaptureRequest.seq = fdp.ConsumeIntegral<uint32_t>();
 }
 
 } // namespace
@@ -145,7 +147,6 @@
     // Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the
     // TouchpadInputMapper constructor.
     setTouchpadSettings(*fdp, policyConfig);
-    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
     TouchpadInputMapper& mapper =
             getMapperForDevice<ThreadSafeFuzzedDataProvider, TouchpadInputMapper>(*fdp, device,
                                                                                   policyConfig);
@@ -164,7 +165,6 @@
                 [&]() -> void { mapper.getSources(); },
                 [&]() -> void {
                     setTouchpadSettings(*fdp, policyConfig);
-                    policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
                     std::list<NotifyArgs> unused =
                             mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
                                                InputReaderConfiguration::Change(
@@ -175,7 +175,7 @@
                 },
                 [&]() -> void {
                     RawEvent event = getFuzzedRawEvent(*fdp);
-                    std::list<NotifyArgs> unused = mapper.process(&event);
+                    std::list<NotifyArgs> unused = mapper.process(event);
                 },
         })();
     }
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 1f72e8b..4f65e77 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -17,9 +17,9 @@
         "PowerHalController.cpp",
         "PowerHalLoader.cpp",
         "PowerHalWrapper.cpp",
+        "PowerHintSessionWrapper.cpp",
         "PowerSaveState.cpp",
         "Temperature.cpp",
-        "WorkDuration.cpp",
         "WorkSource.cpp",
         ":libpowermanager_aidl",
     ],
@@ -51,6 +51,10 @@
         "android.hardware.power@1.3",
     ],
 
+    whole_static_libs: [
+        "android.os.hintmanager_aidl-ndk",
+    ],
+
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index bc178bc..40fd097 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -57,6 +57,10 @@
     PowerHalLoader::unloadAll();
 }
 
+int32_t HalConnector::getAidlVersion() {
+    return PowerHalLoader::getAidlVersion();
+}
+
 // -------------------------------------------------------------------------------------------------
 
 void PowerHalController::init() {
@@ -77,6 +81,22 @@
     return mConnectedHal;
 }
 
+// Using statement expression macro instead of a method lets the static be
+// scoped to the outer method while dodging the need for a support lookup table
+// This only works for AIDL methods that do not vary supported/unsupported depending
+// on their arguments (not setBoost, setMode) which do their own support checks
+#define CACHE_SUPPORT(version, method)                                    \
+    ({                                                                    \
+        static bool support = mHalConnector->getAidlVersion() >= version; \
+        !support ? decltype(method)::unsupported() : ({                   \
+            auto result = method;                                         \
+            if (result.isUnsupported()) {                                 \
+                support = false;                                          \
+            }                                                             \
+            std::move(result);                                            \
+        });                                                               \
+    })
+
 // Check if a call to Power HAL function failed; if so, log the failure and
 // invalidate the current Power HAL handle.
 template <typename T>
@@ -103,40 +123,49 @@
     return processHalResult(handle->setMode(mode, enabled), "setMode");
 }
 
-HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-PowerHalController::createHintSession(int32_t tgid, int32_t uid,
-                                      const std::vector<int32_t>& threadIds,
-                                      int64_t durationNanos) {
+// Aidl-only methods
+
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSession(
+        int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->createHintSession(tgid, uid, threadIds, durationNanos),
-                            "createHintSession");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->createHintSession(tgid, uid, threadIds,
+                                                                    durationNanos),
+                                          "createHintSession"));
 }
 
-HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
-PowerHalController::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> PowerHalController::createHintSessionWithConfig(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
         aidl::android::hardware::power::SessionTag tag,
         aidl::android::hardware::power::SessionConfig* config) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
-                                                                tag, config),
-                            "createHintSessionWithConfig");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->createHintSessionWithConfig(tgid, uid, threadIds,
+                                                                              durationNanos, tag,
+                                                                              config),
+                                          "createHintSessionWithConfig"));
 }
 
 HalResult<int64_t> PowerHalController::getHintSessionPreferredRate() {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->getHintSessionPreferredRate(), "getHintSessionPreferredRate");
+    return CACHE_SUPPORT(2,
+                         processHalResult(handle->getHintSessionPreferredRate(),
+                                          "getHintSessionPreferredRate"));
 }
 
 HalResult<aidl::android::hardware::power::ChannelConfig> PowerHalController::getSessionChannel(
         int tgid, int uid) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->getSessionChannel(tgid, uid), "getSessionChannel");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->getSessionChannel(tgid, uid),
+                                          "getSessionChannel"));
 }
 
 HalResult<void> PowerHalController::closeSessionChannel(int tgid, int uid) {
     std::shared_ptr<HalWrapper> handle = initHal();
-    return processHalResult(handle->closeSessionChannel(tgid, uid), "closeSessionChannel");
+    return CACHE_SUPPORT(5,
+                         processHalResult(handle->closeSessionChannel(tgid, uid),
+                                          "closeSessionChannel"));
 }
 
 } // namespace power
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 2214461..ea284c3 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -60,6 +60,7 @@
 sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
 sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
 sp<V1_3::IPower> PowerHalLoader::gHalHidlV1_3 = nullptr;
+int32_t PowerHalLoader::gAidlInterfaceVersion = 0;
 
 void PowerHalLoader::unloadAll() {
     std::lock_guard<std::mutex> lock(gHalMutex);
@@ -89,6 +90,8 @@
             ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str())));
     if (gHalAidl) {
         ALOGI("Successfully connected to Power HAL AIDL service.");
+        gHalAidl->getInterfaceVersion(&gAidlInterfaceVersion);
+
     } else {
         ALOGI("Power HAL AIDL service not available.");
         gHalExists = false;
@@ -128,6 +131,10 @@
     return loadHal<V1_0::IPower>(gHalExists, gHalHidlV1_0, loadFn, "HIDL v1.0");
 }
 
+int32_t PowerHalLoader::getAidlVersion() {
+    return gAidlInterfaceVersion;
+}
+
 // -------------------------------------------------------------------------------------------------
 
 } // namespace power
diff --git a/services/powermanager/PowerHalWrapper.cpp b/services/powermanager/PowerHalWrapper.cpp
index 1009100..bd6685c 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -18,11 +18,10 @@
 #include <aidl/android/hardware/power/Boost.h>
 #include <aidl/android/hardware/power/IPowerHintSession.h>
 #include <aidl/android/hardware/power/Mode.h>
+#include <powermanager/HalResult.h>
 #include <powermanager/PowerHalWrapper.h>
 #include <utils/Log.h>
 
-#include <cinttypes>
-
 using namespace android::hardware::power;
 namespace Aidl = aidl::android::hardware::power;
 
@@ -31,15 +30,6 @@
 namespace power {
 
 // -------------------------------------------------------------------------------------------------
-inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) {
-    if (result.isOk()) {
-        return HalResult<void>::ok();
-    }
-    ALOGE("Power HAL request failed: %s", result.getDescription().c_str());
-    return HalResult<void>::failed(result.getDescription());
-}
-
-// -------------------------------------------------------------------------------------------------
 
 HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
     ALOGV("Skipped setBoost %s with duration %dms because %s", toString(boost).c_str(), durationMs,
@@ -53,19 +43,19 @@
     return HalResult<void>::unsupported();
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSession(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
     ALOGV("Skipped createHintSession(task num=%zu) because %s", threadIds.size(),
           getUnsupportedMessage());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> EmptyHalWrapper::createHintSessionWithConfig(
         int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t, Aidl::SessionTag,
         Aidl::SessionConfig*) {
     ALOGV("Skipped createHintSessionWithConfig(task num=%zu) because %s", threadIds.size(),
           getUnsupportedMessage());
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::unsupported();
 }
 
 HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
@@ -225,7 +215,7 @@
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setBoost(boost, durationMs));
+    return HalResult<void>::fromStatus(mHandle->setBoost(boost, durationMs));
 }
 
 HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
@@ -253,25 +243,25 @@
     }
     lock.unlock();
 
-    return toHalResult(mHandle->setMode(mode, enabled));
+    return HalResult<void>::fromStatus(mHandle->setMode(mode, enabled));
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSession(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
             fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
-                       std::move(appSession));
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
 }
 
-HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSessionWithConfig(
+HalResult<std::shared_ptr<PowerHintSessionWrapper>> AidlHalWrapper::createHintSessionWithConfig(
         int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos,
         Aidl::SessionTag tag, Aidl::SessionConfig* config) {
     std::shared_ptr<Aidl::IPowerHintSession> appSession;
-    return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
+    return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
             fromStatus(mHandle->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos,
                                                             tag, config, &appSession),
-                       std::move(appSession));
+                       std::make_shared<PowerHintSessionWrapper>(std::move(appSession)));
 }
 
 HalResult<int64_t> AidlHalWrapper::getHintSessionPreferredRate() {
@@ -287,7 +277,7 @@
 }
 
 HalResult<void> AidlHalWrapper::closeSessionChannel(int tgid, int uid) {
-    return toHalResult(mHandle->closeSessionChannel(tgid, uid));
+    return HalResult<void>::fromStatus(mHandle->closeSessionChannel(tgid, uid));
 }
 
 const char* AidlHalWrapper::getUnsupportedMessage() {
diff --git a/services/powermanager/PowerHintSessionWrapper.cpp b/services/powermanager/PowerHintSessionWrapper.cpp
new file mode 100644
index 0000000..930c7fa
--- /dev/null
+++ b/services/powermanager/PowerHintSessionWrapper.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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 <powermanager/PowerHintSessionWrapper.h>
+
+using namespace aidl::android::hardware::power;
+
+namespace android::power {
+
+// Caches support for a given call in a static variable, checking both
+// the return value and interface version.
+#define CACHE_SUPPORT(version, method)                      \
+    ({                                                      \
+        static bool support = mInterfaceVersion >= version; \
+        !support ? decltype(method)::unsupported() : ({     \
+            auto result = method;                           \
+            if (result.isUnsupported()) {                   \
+                support = false;                            \
+            }                                               \
+            std::move(result);                              \
+        });                                                 \
+    })
+
+#define CHECK_SESSION(resultType)                                    \
+    if (mSession == nullptr) {                                       \
+        return HalResult<resultType>::failed("Session not running"); \
+    }
+
+// FWD_CALL just forwards calls from the wrapper to the session object.
+// It only works if the call has no return object, as is the case with all calls
+// except getSessionConfig.
+#define FWD_CALL(version, name, args, untypedArgs)                                              \
+    HalResult<void> PowerHintSessionWrapper::name args {                                        \
+        CHECK_SESSION(void)                                                                     \
+        return CACHE_SUPPORT(version, HalResult<void>::fromStatus(mSession->name untypedArgs)); \
+    }
+
+PowerHintSessionWrapper::PowerHintSessionWrapper(std::shared_ptr<IPowerHintSession>&& session)
+      : mSession(session) {
+    if (mSession != nullptr) {
+        mSession->getInterfaceVersion(&mInterfaceVersion);
+    }
+}
+
+// Support for individual hints/modes is not really handled here since there
+// is no way to check for it, so in the future if a way to check that is added,
+// this will need to be updated.
+
+FWD_CALL(2, updateTargetWorkDuration, (int64_t in_targetDurationNanos), (in_targetDurationNanos));
+FWD_CALL(2, reportActualWorkDuration, (const std::vector<WorkDuration>& in_durations),
+         (in_durations));
+FWD_CALL(2, pause, (), ());
+FWD_CALL(2, resume, (), ());
+FWD_CALL(2, close, (), ());
+FWD_CALL(4, sendHint, (SessionHint in_hint), (in_hint));
+FWD_CALL(4, setThreads, (const std::vector<int32_t>& in_threadIds), (in_threadIds));
+FWD_CALL(5, setMode, (SessionMode in_type, bool in_enabled), (in_type, in_enabled));
+
+HalResult<SessionConfig> PowerHintSessionWrapper::getSessionConfig() {
+    CHECK_SESSION(SessionConfig);
+    SessionConfig config;
+    return CACHE_SUPPORT(5,
+                         HalResult<SessionConfig>::fromStatus(mSession->getSessionConfig(&config),
+                                                              std::move(config)));
+}
+
+} // namespace android::power
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
deleted file mode 100644
index bd2b10a..0000000
--- a/services/powermanager/WorkDuration.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * 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.
- */
-
-#define LOG_TAG "WorkDuration"
-
-#include <android/WorkDuration.h>
-#include <android/performance_hint.h>
-#include <binder/Parcel.h>
-#include <utils/Log.h>
-
-namespace android::os {
-
-WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
-                           int64_t cpuDurationNanos, int64_t gpuDurationNanos)
-      : timestampNanos(0),
-        actualTotalDurationNanos(totalDurationNanos),
-        workPeriodStartTimestampNanos(startTimestampNanos),
-        actualCpuDurationNanos(cpuDurationNanos),
-        actualGpuDurationNanos(gpuDurationNanos) {}
-
-status_t WorkDuration::writeToParcel(Parcel* parcel) const {
-    if (parcel == nullptr) {
-        ALOGE("%s: Null parcel", __func__);
-        return BAD_VALUE;
-    }
-
-    parcel->writeInt64(workPeriodStartTimestampNanos);
-    parcel->writeInt64(actualTotalDurationNanos);
-    parcel->writeInt64(actualCpuDurationNanos);
-    parcel->writeInt64(actualGpuDurationNanos);
-    parcel->writeInt64(timestampNanos);
-    return OK;
-}
-
-status_t WorkDuration::readFromParcel(const Parcel*) {
-    return INVALID_OPERATION;
-}
-
-} // namespace android::os
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
deleted file mode 100644
index 26a575f..0000000
--- a/services/powermanager/include/android/WorkDuration.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/**
- * 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 <binder/Parcelable.h>
-#include <math.h>
-
-struct AWorkDuration {};
-
-namespace android::os {
-
-/**
- * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
- * binder calls.
- * This file needs to be kept in sync with the WorkDuration in
- * frameworks/base/core/java/android/os/WorkDuration.java
- */
-struct WorkDuration : AWorkDuration, android::Parcelable {
-    WorkDuration() = default;
-    ~WorkDuration() = default;
-
-    WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
-                 int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
-    status_t writeToParcel(Parcel* parcel) const override;
-    status_t readFromParcel(const Parcel* parcel) override;
-
-    inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
-        return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
-                actualTotalDurationNanos == other.actualTotalDurationNanos &&
-                actualCpuDurationNanos == other.actualCpuDurationNanos &&
-                actualGpuDurationNanos == other.actualGpuDurationNanos;
-    }
-
-    bool operator==(const WorkDuration& other) const {
-        return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
-    }
-
-    bool operator!=(const WorkDuration& other) const { return !(*this == other); }
-
-    friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
-        os << "{"
-           << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
-           << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
-           << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
-           << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
-           << ", timestampNanos: " << workDuration.timestampNanos << "}";
-        return os;
-    }
-
-    int64_t timestampNanos;
-    int64_t actualTotalDurationNanos;
-    int64_t workPeriodStartTimestampNanos;
-    int64_t actualCpuDurationNanos;
-    int64_t actualGpuDurationNanos;
-};
-
-} // namespace android::os
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 6fc96c0..a05ce2b 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -37,6 +37,7 @@
         "PowerHalWrapperHidlV1_1Test.cpp",
         "PowerHalWrapperHidlV1_2Test.cpp",
         "PowerHalWrapperHidlV1_3Test.cpp",
+        "PowerHintSessionWrapperTest.cpp",
         "WorkSourceTest.cpp",
     ],
     cflags: [
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index a720296..1589c99 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -86,6 +86,10 @@
 
 void PowerHalWrapperAidlTest::SetUp() {
     mMockHal = ndk::SharedRefBase::make<StrictMock<MockIPower>>();
+    EXPECT_CALL(*mMockHal, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
     mWrapper = std::make_unique<AidlHalWrapper>(mMockHal);
     ASSERT_NE(nullptr, mWrapper);
 }
@@ -130,10 +134,12 @@
 }
 
 TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) {
-    EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
-            .Times(Exactly(1))
-            .WillOnce(DoAll(SetArgPointee<1>(false),
-                            Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+    EXPECT_CALL(*mMockHal.get(), isBoostSupported(_, _))
+            .Times(Exactly(2))
+            .WillRepeatedly([](Boost, bool* ret) {
+                *ret = false;
+                return ndk::ScopedAStatus::ok();
+            });
 
     auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
     ASSERT_TRUE(result.isUnsupported());
@@ -311,3 +317,29 @@
     auto closeResult = mWrapper->closeSessionChannel(tgid, uid);
     ASSERT_TRUE(closeResult.isOk());
 }
+
+TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionWithConfigUnsupported) {
+    std::vector<int> threadIds{gettid()};
+    int32_t tgid = 999;
+    int32_t uid = 1001;
+    int64_t durationNanos = 16666666L;
+    SessionTag tag = SessionTag::OTHER;
+    SessionConfig out;
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .Times(1)
+            .WillOnce(Return(testing::ByMove(
+                    ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION))));
+    auto result =
+            mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+    Mock::VerifyAndClearExpectations(mMockHal.get());
+    EXPECT_CALL(*mMockHal.get(),
+                createHintSessionWithConfig(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos),
+                                            Eq(tag), _, _))
+            .WillOnce(Return(
+                    testing::ByMove(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION))));
+    result = mWrapper->createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, tag, &out);
+    ASSERT_TRUE(result.isUnsupported());
+}
diff --git a/services/powermanager/tests/PowerHintSessionWrapperTest.cpp b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
new file mode 100644
index 0000000..7743fa4
--- /dev/null
+++ b/services/powermanager/tests/PowerHintSessionWrapperTest.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 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 <aidl/android/hardware/power/IPowerHintSession.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using android::power::PowerHintSessionWrapper;
+
+using namespace android;
+using namespace std::chrono_literals;
+using namespace testing;
+
+class MockIPowerHintSession : public IPowerHintSession {
+public:
+    MockIPowerHintSession() = default;
+    MOCK_METHOD(::ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t in_targetDurationNanos),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, reportActualWorkDuration,
+                (const std::vector<::aidl::android::hardware::power::WorkDuration>& in_durations),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, pause, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, resume, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, close, (), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, sendHint,
+                (::aidl::android::hardware::power::SessionHint in_hint), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setThreads, (const std::vector<int32_t>& in_threadIds),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, setMode,
+                (::aidl::android::hardware::power::SessionMode in_type, bool in_enabled),
+                (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getSessionConfig,
+                (::aidl::android::hardware::power::SessionConfig * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+    MOCK_METHOD(::ndk::ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+    MOCK_METHOD(::ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+class PowerHintSessionWrapperTest : public Test {
+public:
+    void SetUp() override;
+
+protected:
+    std::shared_ptr<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
+    std::unique_ptr<PowerHintSessionWrapper> mSession = nullptr;
+};
+
+void PowerHintSessionWrapperTest::SetUp() {
+    mMockSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    EXPECT_CALL(*mMockSession, getInterfaceVersion(_)).WillRepeatedly(([](int32_t* ret) {
+        *ret = 5;
+        return ndk::ScopedAStatus::ok();
+    }));
+    mSession = std::make_unique<PowerHintSessionWrapper>(mMockSession);
+    ASSERT_NE(nullptr, mSession);
+}
+
+TEST_F(PowerHintSessionWrapperTest, updateTargetWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1000000000))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->updateTargetWorkDuration(1000000000);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, reportActualWorkDuration) {
+    EXPECT_CALL(*mMockSession.get(),
+                reportActualWorkDuration(
+                        std::vector<::aidl::android::hardware::power::WorkDuration>()))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->reportActualWorkDuration(
+            std::vector<::aidl::android::hardware::power::WorkDuration>());
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, pause) {
+    EXPECT_CALL(*mMockSession.get(), pause()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->pause();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, resume) {
+    EXPECT_CALL(*mMockSession.get(), resume()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->resume();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, close) {
+    EXPECT_CALL(*mMockSession.get(), close()).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->close();
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, sendHint) {
+    EXPECT_CALL(*mMockSession.get(),
+                sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->sendHint(::aidl::android::hardware::power::SessionHint::CPU_LOAD_UP);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setThreads) {
+    EXPECT_CALL(*mMockSession.get(), setThreads(_)).WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setThreads(std::vector<int32_t>{gettid()});
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, setMode) {
+    EXPECT_CALL(*mMockSession.get(),
+                setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY, true))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
+    auto status = mSession->setMode(::aidl::android::hardware::power::SessionMode::POWER_EFFICIENCY,
+                                    true);
+    ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(PowerHintSessionWrapperTest, getSessionConfig) {
+    EXPECT_CALL(*mMockSession.get(), getSessionConfig(_))
+            .WillOnce(DoAll(SetArgPointee<0>(
+                                    aidl::android::hardware::power::SessionConfig{.id = 12L}),
+                            Return(ndk::ScopedAStatus::ok())));
+    auto status = mSession->getSessionConfig();
+    ASSERT_TRUE(status.isOk());
+}
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index afaf0ae..7b2596a 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -52,6 +52,7 @@
         "-Wall",
         "-Werror",
         "-Wextra",
+        "-Wthread-safety",
         "-fvisibility=hidden",
     ],
 
@@ -84,6 +85,7 @@
         "android.hardware.common-V2-ndk",
         "android.hardware.common.fmq-V1-ndk",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
 
     static_libs: [
diff --git a/services/sensorservice/OWNERS b/services/sensorservice/OWNERS
index 7347ac7..3ccdc17 100644
--- a/services/sensorservice/OWNERS
+++ b/services/sensorservice/OWNERS
@@ -1 +1,3 @@
 bduddie@google.com
+arthuri@google.com
+rockyfang@google.com
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index f62562c..9c4d1ac 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -429,14 +429,18 @@
 }
 
 void SensorDevice::onDynamicSensorsDisconnected(
-        const std::vector<int32_t>& dynamicSensorHandlesRemoved) {
-    if (sensorservice_flags::sensor_device_on_dynamic_sensor_disconnected()) {
-        for (auto handle : dynamicSensorHandlesRemoved) {
-            auto it = mConnectedDynamicSensors.find(handle);
-            if (it != mConnectedDynamicSensors.end()) {
-                mConnectedDynamicSensors.erase(it);
-            }
-        }
+        const std::vector<int32_t>& /*dynamicSensorHandlesRemoved*/) {
+    // This function is currently a no-op has removing data in mConnectedDynamicSensors here will
+    // cause a race condition between when this callback is invoked and when the dynamic sensor meta
+    // event is processed by polling. The clean up should only happen after processing the meta
+    // event. See the call stack of cleanupDisconnectedDynamicSensor.
+}
+
+void SensorDevice::cleanupDisconnectedDynamicSensor(int handle) {
+    std::lock_guard<std::mutex> lock(mDynamicSensorsMutex);
+    auto it = mConnectedDynamicSensors.find(handle);
+    if (it != mConnectedDynamicSensors.end()) {
+        mConnectedDynamicSensors.erase(it);
     }
 }
 
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index 52f7cf2..b7b04b5 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -63,6 +63,14 @@
     std::vector<int32_t> getDynamicSensorHandles();
 
     void handleDynamicSensorConnection(int handle, bool connected);
+    /**
+     * Removes handle from connected dynamic sensor list. Note that this method must be called after
+     * SensorService has done using sensor data.
+     *
+     * @param handle of the disconnected dynamic sensor.
+     */
+    void cleanupDisconnectedDynamicSensor(int handle);
+
     status_t initCheck() const;
     int getHalDeviceVersion() const;
 
diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp
index 555b80a..33724a9 100644
--- a/services/sensorservice/SensorDirectConnection.cpp
+++ b/services/sensorservice/SensorDirectConnection.cpp
@@ -26,12 +26,17 @@
 
 using util::ProtoOutputStream;
 
-SensorService::SensorDirectConnection::SensorDirectConnection(const sp<SensorService>& service,
-        uid_t uid, const sensors_direct_mem_t *mem, int32_t halChannelHandle,
-        const String16& opPackageName, int deviceId)
-        : mService(service), mUid(uid), mMem(*mem),
+SensorService::SensorDirectConnection::SensorDirectConnection(
+        const sp<SensorService>& service, uid_t uid, pid_t pid, const sensors_direct_mem_t* mem,
+        int32_t halChannelHandle, const String16& opPackageName, int deviceId)
+      : mService(service),
+        mUid(uid),
+        mPid(pid),
+        mMem(*mem),
         mHalChannelHandle(halChannelHandle),
-        mOpPackageName(opPackageName), mDeviceId(deviceId), mDestroyed(false) {
+        mOpPackageName(opPackageName),
+        mDeviceId(deviceId),
+        mDestroyed(false) {
     mUserId = multiuser_get_user_id(mUid);
     ALOGD_IF(DEBUG_CONNECTIONS, "Created SensorDirectConnection");
 }
@@ -62,10 +67,21 @@
 
 void SensorService::SensorDirectConnection::dump(String8& result) const {
     Mutex::Autolock _l(mConnectionLock);
-    result.appendFormat("\tPackage %s, HAL channel handle %d, total sensor activated %zu\n",
-            String8(mOpPackageName).c_str(), getHalChannelHandle(), mActivated.size());
-    for (auto &i : mActivated) {
-        result.appendFormat("\t\tSensor %#08x, rate %d\n", i.first, i.second);
+    result.appendFormat("\t%s | HAL channel handle %d | uid %d | pid %d\n",
+                        String8(mOpPackageName).c_str(), getHalChannelHandle(), mUid, mPid);
+    result.appendFormat("\tActivated sensor count: %zu\n", mActivated.size());
+    dumpSensorInfoWithLock(result, mActivated);
+
+    result.appendFormat("\tBackup sensor (opened but UID idle) count: %zu\n",
+                        mActivatedBackup.size());
+    dumpSensorInfoWithLock(result, mActivatedBackup);
+}
+
+void SensorService::SensorDirectConnection::dumpSensorInfoWithLock(
+        String8& result, std::unordered_map<int, int> sensors) const {
+    for (auto& i : sensors) {
+        result.appendFormat("\t\t%s 0x%08x | rate %d\n", mService->getSensorName(i.first).c_str(),
+                            i.first, i.second);
     }
 }
 
diff --git a/services/sensorservice/SensorDirectConnection.h b/services/sensorservice/SensorDirectConnection.h
index bfaf811..9f21731 100644
--- a/services/sensorservice/SensorDirectConnection.h
+++ b/services/sensorservice/SensorDirectConnection.h
@@ -17,9 +17,10 @@
 #ifndef ANDROID_SENSOR_DIRECT_CONNECTION_H
 #define ANDROID_SENSOR_DIRECT_CONNECTION_H
 
-#include <optional>
+#include <android-base/thread_annotations.h>
 #include <stdint.h>
 #include <sys/types.h>
+#include <optional>
 
 #include <binder/BinderService.h>
 
@@ -37,15 +38,15 @@
 
 class SensorService::SensorDirectConnection: public BnSensorEventConnection {
 public:
-    SensorDirectConnection(const sp<SensorService>& service, uid_t uid,
-            const sensors_direct_mem_t *mem, int32_t halChannelHandle,
-            const String16& opPackageName, int deviceId);
+    SensorDirectConnection(const sp<SensorService>& service, uid_t uid, pid_t pid,
+                           const sensors_direct_mem_t* mem, int32_t halChannelHandle,
+                           const String16& opPackageName, int deviceId);
     void dump(String8& result) const;
     void dump(util::ProtoOutputStream* proto) const;
     uid_t getUid() const { return mUid; }
     const String16& getOpPackageName() const { return mOpPackageName; }
     int32_t getHalChannelHandle() const;
-    bool isEquivalent(const sensors_direct_mem_t *mem) const;
+    bool isEquivalent(const sensors_direct_mem_t* mem) const;
 
     // Invoked when access to sensors for this connection has changed, e.g. lost or
     // regained due to changes in the sensor restricted/privacy mode or the
@@ -94,8 +95,13 @@
     // Recover sensor requests previously capped by capRates().
     void uncapRates();
 
+    // Dumps a set of sensor infos.
+    void dumpSensorInfoWithLock(String8& result, std::unordered_map<int, int> sensors) const
+            EXCLUSIVE_LOCKS_REQUIRED(mConnectionLock);
+
     const sp<SensorService> mService;
     const uid_t mUid;
+    const pid_t mPid;
     const sensors_direct_mem_t mMem;
     const int32_t mHalChannelHandle;
     const String16 mOpPackageName;
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index dc86577..0d00642 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -90,15 +90,14 @@
         result.append("NORMAL\n");
     }
     result.appendFormat("\t %s | WakeLockRefCount %d | uid %d | cache size %d | "
-            "max cache size %d\n", mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize,
-            mMaxCacheSize);
+                        "max cache size %d | has sensor access: %s\n",
+                        mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize, mMaxCacheSize,
+                        hasSensorAccess() ? "true" : "false");
     for (auto& it : mSensorInfo) {
         const FlushInfo& flushInfo = it.second;
-        result.appendFormat("\t %s 0x%08x | status: %s | pending flush events %d \n",
-                            mService->getSensorName(it.first).c_str(),
-                            it.first,
-                            flushInfo.mFirstFlushPending ? "First flush pending" :
-                                                           "active",
+        result.appendFormat("\t %s 0x%08x | first flush pending: %s | pending flush events %d \n",
+                            mService->getSensorName(it.first).c_str(), it.first,
+                            flushInfo.mFirstFlushPending ? "true" : "false",
                             flushInfo.mPendingFlushEventsToSend);
     }
 #if DEBUG_CONNECTIONS
@@ -173,7 +172,7 @@
 
 bool SensorService::SensorEventConnection::removeSensor(int32_t handle) {
     Mutex::Autolock _l(mConnectionLock);
-    if (mSensorInfo.erase(handle) >= 0) {
+    if (mSensorInfo.erase(handle) > 0) {
         return true;
     }
     return false;
@@ -461,16 +460,18 @@
         // is pre-Q, still permit delivering events to the app even if permission isn't granted
         // (since this permission was only introduced in Q)
         if ((event.type == SENSOR_TYPE_STEP_COUNTER || event.type == SENSOR_TYPE_STEP_DETECTOR) &&
-                mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            mTargetSdk > 0 && mTargetSdk <= __ANDROID_API_P__) {
+            success = true;
+        } else if (mUid == AID_SYSTEM) {
+            // Allow access if it is requested from system.
             success = true;
         } else {
             int32_t sensorHandle = event.sensor;
             String16 noteMsg("Sensor event (");
             noteMsg.append(String16(mService->getSensorStringType(sensorHandle)));
             noteMsg.append(String16(")"));
-            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid,
-                                                                mOpPackageName, mAttributionTag,
-                                                                noteMsg);
+            int32_t appOpMode = mService->sAppOpsManager.noteOp(iter->second, mUid, mOpPackageName,
+                                                                mAttributionTag, noteMsg);
             success = (appOpMode == AppOpsManager::MODE_ALLOWED);
         }
     }
@@ -710,14 +711,17 @@
         if (err == OK && isSensorCapped) {
             if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
                 !isRateCappedBasedOnPermission()) {
+                Mutex::Autolock _l(mConnectionLock);
                 mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
             } else {
+                Mutex::Autolock _l(mConnectionLock);
                 mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
             }
         }
 
     } else {
         err = mService->disable(this, handle);
+        Mutex::Autolock _l(mConnectionLock);
         mMicSamplingPeriodBackup.erase(handle);
     }
     return err;
@@ -749,8 +753,10 @@
     if (ret == OK && isSensorCapped) {
         if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
             !isRateCappedBasedOnPermission()) {
+            Mutex::Autolock _l(mConnectionLock);
             mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
         } else {
+            Mutex::Autolock _l(mConnectionLock);
             mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
         }
     }
diff --git a/services/sensorservice/SensorEventConnection.h b/services/sensorservice/SensorEventConnection.h
index 6a98a40..bb8733d 100644
--- a/services/sensorservice/SensorEventConnection.h
+++ b/services/sensorservice/SensorEventConnection.h
@@ -199,7 +199,8 @@
     // valid mapping for sensors that require a permission in order to reduce the lookup time.
     std::unordered_map<int32_t, int32_t> mHandleToAppOp;
     // Mapping of sensor handles to its rate before being capped by the mic toggle.
-    std::unordered_map<int, nsecs_t> mMicSamplingPeriodBackup;
+    std::unordered_map<int, nsecs_t> mMicSamplingPeriodBackup
+        GUARDED_BY(mConnectionLock);
     userid_t mUserId;
 
     std::optional<bool> mIsRateCappedBasedOnPermission;
diff --git a/services/sensorservice/SensorRegistrationInfo.h b/services/sensorservice/SensorRegistrationInfo.h
index dc9e821..a8a773a 100644
--- a/services/sensorservice/SensorRegistrationInfo.h
+++ b/services/sensorservice/SensorRegistrationInfo.h
@@ -38,10 +38,11 @@
         mActivated = false;
     }
 
-    SensorRegistrationInfo(int32_t handle, const String8 &packageName,
-                           int64_t samplingRateNs, int64_t maxReportLatencyNs, bool activate) {
+    SensorRegistrationInfo(int32_t handle, const String8& packageName, int64_t samplingRateNs,
+                           int64_t maxReportLatencyNs, bool activate, status_t registerStatus) {
         mSensorHandle = handle;
         mPackageName = packageName;
+        mRegisteredStatus = registerStatus;
 
         mSamplingRateUs = static_cast<int64_t>(samplingRateNs/1000);
         mMaxReportLatencyUs = static_cast<int64_t>(maxReportLatencyNs/1000);
@@ -60,28 +61,43 @@
        return (info.mSensorHandle == INT32_MIN && info.mRealtimeSec == 0);
     }
 
-    // Dumpable interface
-    virtual std::string dump() const override {
+    std::string dump(SensorService* sensorService) const {
         struct tm* timeinfo = localtime(&mRealtimeSec);
         const int8_t hour = static_cast<int8_t>(timeinfo->tm_hour);
         const int8_t min = static_cast<int8_t>(timeinfo->tm_min);
         const int8_t sec = static_cast<int8_t>(timeinfo->tm_sec);
 
         std::ostringstream ss;
-        ss << std::setfill('0') << std::setw(2) << static_cast<int>(hour) << ":"
-           << std::setw(2) << static_cast<int>(min) << ":"
-           << std::setw(2) << static_cast<int>(sec)
-           << (mActivated ? " +" : " -")
-           << " 0x" << std::hex << std::setw(8) << mSensorHandle << std::dec
-           << std::setfill(' ') << " pid=" << std::setw(5) << mPid
-           << " uid=" << std::setw(5) << mUid << " package=" << mPackageName;
-        if (mActivated) {
-           ss  << " samplingPeriod=" << mSamplingRateUs << "us"
-               << " batchingPeriod=" << mMaxReportLatencyUs << "us";
-        };
+        ss << std::setfill('0') << std::setw(2) << static_cast<int>(hour) << ":" << std::setw(2)
+           << static_cast<int>(min) << ":" << std::setw(2) << static_cast<int>(sec)
+           << (mActivated ? " +" : " -") << " 0x" << std::hex << std::setw(8) << mSensorHandle;
+        ss << std::dec << std::setfill(' ') << " pid=" << std::setw(5) << mPid
+           << " uid=" << std::setw(5) << mUid;
+
+        std::string samplingRate =
+            mActivated ? std::to_string(mSamplingRateUs) : "  N/A  ";
+        std::string batchingInterval =
+            mActivated ? std::to_string(mMaxReportLatencyUs) : "  N/A  ";
+        ss << " samplingPeriod=" << std::setfill(' ') << std::setw(8)
+           << samplingRate << "us" << " batchingPeriod=" << std::setfill(' ')
+           << std::setw(8) << batchingInterval << "us"
+           << " result=" << statusToString(mRegisteredStatus)
+           << " (sensor, package): (" << std::setfill(' ') << std::left
+           << std::setw(27);
+
+        if (sensorService != nullptr) {
+          ss << sensorService->getSensorName(mSensorHandle);
+        } else {
+          ss << "null";
+        }
+        ss << ", " << mPackageName << ")";
+
         return ss.str();
     }
 
+    // Dumpable interface
+    virtual std::string dump() const override { return dump(static_cast<SensorService*>(nullptr)); }
+
     /**
      * Dump debugging information as android.service.SensorRegistrationInfoProto protobuf message
      * using ProtoOutputStream.
@@ -110,6 +126,7 @@
     int64_t mMaxReportLatencyUs;
     bool mActivated;
     time_t mRealtimeSec;
+    status_t mRegisteredStatus;
 };
 
 } // namespace android;
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index e1c43c6..060508c 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -682,14 +682,14 @@
                     mSensorPrivacyPolicy->isSensorPrivacyEnabled() ? "enabled" : "disabled");
 
             const auto& activeConnections = connLock.getActiveConnections();
-            result.appendFormat("%zd active connections\n", activeConnections.size());
+            result.appendFormat("%zd open event connections\n", activeConnections.size());
             for (size_t i=0 ; i < activeConnections.size() ; i++) {
                 result.appendFormat("Connection Number: %zu \n", i);
                 activeConnections[i]->dump(result);
             }
 
             const auto& directConnections = connLock.getDirectConnections();
-            result.appendFormat("%zd direct connections\n", directConnections.size());
+            result.appendFormat("%zd open direct connections\n", directConnections.size());
             for (size_t i = 0 ; i < directConnections.size() ; i++) {
                 result.appendFormat("Direct connection %zu:\n", i);
                 directConnections[i]->dump(result);
@@ -708,7 +708,7 @@
                         SENSOR_REGISTRATIONS_BUF_SIZE;
                     continue;
                 }
-                result.appendFormat("%s\n", reg_info.dump().c_str());
+                result.appendFormat("%s\n", reg_info.dump(this).c_str());
                 currentIndex = (currentIndex - 1 + SENSOR_REGISTRATIONS_BUF_SIZE) %
                         SENSOR_REGISTRATIONS_BUF_SIZE;
             } while(startIndex != currentIndex);
@@ -1273,6 +1273,10 @@
                 } else {
                     int handle = mSensorEventBuffer[i].dynamic_sensor_meta.handle;
                     disconnectDynamicSensor(handle, activeConnections);
+                    if (sensorservice_flags::
+                            sensor_service_clear_dynamic_sensor_data_at_the_end()) {
+                      device.cleanupDisconnectedDynamicSensor(handle);
+                    }
                 }
             }
         }
@@ -1579,7 +1583,11 @@
     // Only 4 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION,
     // REPLAY_DATA_INJECTION and HAL_BYPASS_REPLAY_DATA_INJECTION
     if (requestedMode != NORMAL && !isInjectionMode(requestedMode)) {
-        return nullptr;
+      ALOGE(
+          "Failed to create sensor event connection: invalid request mode. "
+          "requestMode: %d",
+          requestedMode);
+      return nullptr;
     }
     resetTargetSdkVersionCache(opPackageName);
 
@@ -1587,8 +1595,19 @@
     // To create a client in DATA_INJECTION mode to inject data, SensorService should already be
     // operating in DI mode.
     if (requestedMode == DATA_INJECTION) {
-        if (mCurrentOperatingMode != DATA_INJECTION) return nullptr;
-        if (!isAllowListedPackage(packageName)) return nullptr;
+      if (mCurrentOperatingMode != DATA_INJECTION) {
+        ALOGE(
+            "Failed to create sensor event connection: sensor service not in "
+            "DI mode when creating a client in DATA_INJECTION mode");
+        return nullptr;
+      }
+      if (!isAllowListedPackage(packageName)) {
+        ALOGE(
+            "Failed to create sensor event connection: package %s not in "
+            "allowed list for DATA_INJECTION mode",
+            packageName.c_str());
+        return nullptr;
+      }
     }
 
     uid_t uid = IPCThreadState::self()->getCallingUid();
@@ -1725,7 +1744,10 @@
         ALOGE("SensorDevice::registerDirectChannel returns %d", channelHandle);
     } else {
         mem.handle = clone;
-        conn = new SensorDirectConnection(this, uid, &mem, channelHandle, opPackageName, deviceId);
+        IPCThreadState* thread = IPCThreadState::self();
+        pid_t pid = (thread != nullptr) ? thread->getCallingPid() : -1;
+        conn = new SensorDirectConnection(this, uid, pid, &mem, channelHandle, opPackageName,
+                                          deviceId);
     }
 
     if (conn == nullptr) {
@@ -2145,17 +2167,17 @@
                 sensor->getSensor().getRequiredAppOp() >= 0) {
             connection->mHandleToAppOp[handle] = sensor->getSensor().getRequiredAppOp();
         }
-
-        mLastNSensorRegistrations.editItemAt(mNextSensorRegIndex) =
-                SensorRegistrationInfo(handle, connection->getPackageName(),
-                                       samplingPeriodNs, maxBatchReportLatencyNs, true);
-        mNextSensorRegIndex = (mNextSensorRegIndex + 1) % SENSOR_REGISTRATIONS_BUF_SIZE;
     }
 
     if (err != NO_ERROR) {
         // batch/activate has failed, reset our state.
         cleanupWithoutDisableLocked(connection, handle);
     }
+
+    mLastNSensorRegistrations.editItemAt(mNextSensorRegIndex) =
+            SensorRegistrationInfo(handle, connection->getPackageName(), samplingPeriodNs,
+                                   maxBatchReportLatencyNs, /*activate=*/ true, err);
+    mNextSensorRegIndex = (mNextSensorRegIndex + 1) % SENSOR_REGISTRATIONS_BUF_SIZE;
     return err;
 }
 
@@ -2168,13 +2190,10 @@
     if (err == NO_ERROR) {
         std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
         err = sensor != nullptr ? sensor->activate(connection.get(), false) : status_t(BAD_VALUE);
-
     }
-    if (err == NO_ERROR) {
-        mLastNSensorRegistrations.editItemAt(mNextSensorRegIndex) =
-                SensorRegistrationInfo(handle, connection->getPackageName(), 0, 0, false);
-        mNextSensorRegIndex = (mNextSensorRegIndex + 1) % SENSOR_REGISTRATIONS_BUF_SIZE;
-    }
+    mLastNSensorRegistrations.editItemAt(mNextSensorRegIndex) =
+            SensorRegistrationInfo(handle, connection->getPackageName(), 0, 0, /*activate=*/ false, err);
+    mNextSensorRegIndex = (mNextSensorRegIndex + 1) % SENSOR_REGISTRATIONS_BUF_SIZE;
     return err;
 }
 
@@ -2302,11 +2321,16 @@
         // requirement to hold the AR permission to access Step Counter and Step Detector events
         // was introduced.
         canAccess = true;
+    } else if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+        // Allow access if it is requested from system.
+        canAccess = true;
     } else if (hasPermissionForSensor(sensor)) {
-        // Ensure that the AppOp is allowed, or that there is no necessary app op for the sensor
+        // Ensure that the AppOp is allowed, or that there is no necessary app op
+        // for the sensor
         if (opCode >= 0) {
-            const int32_t appOpMode = sAppOpsManager.checkOp(opCode,
-                    IPCThreadState::self()->getCallingUid(), opPackageName);
+            const int32_t appOpMode =
+                    sAppOpsManager.checkOp(opCode, IPCThreadState::self()->getCallingUid(),
+                                           opPackageName);
             canAccess = (appOpMode == AppOpsManager::MODE_ALLOWED);
         } else {
             canAccess = true;
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 118d928..bd54d24 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -340,8 +340,8 @@
             binder::Status onSensorPrivacyChanged(int toggleType, int sensor,
                                                   bool enabled);
 
-            // This callback is used for additional automotive-specific states for sensor privacy
-            // such as AUTO_DRIVER_ASSISTANCE_APPS. The newly defined states will only be valid
+            // This callback is used for additional automotive-specific state for sensor privacy
+            // such as ENABLED_EXCEPT_ALLOWLISTED_APPS. The newly defined states will only be valid
             // for camera privacy on automotive devices. onSensorPrivacyChanged() will still be
             // invoked whenever the enabled status of a toggle changes.
             binder::Status onSensorPrivacyStateChanged(int, int, int) {return binder::Status::ok();}
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index f6f104e..b2dc89b 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -26,6 +26,11 @@
         "libfakeservicemanager",
         "libcutils",
         "liblog",
+        "libsensor_flags_c_lib",
+    ],
+    shared_libs: [
+        "libaconfig_storage_read_api_cc",
+        "server_configurable_flags",
     ],
     srcs: [
         "fuzzer.cpp",
diff --git a/services/sensorservice/senserservice_flags.aconfig b/services/sensorservice/senserservice_flags.aconfig
index 8d43f79..7abfbaa 100644
--- a/services/sensorservice/senserservice_flags.aconfig
+++ b/services/sensorservice/senserservice_flags.aconfig
@@ -13,4 +13,18 @@
   namespace: "sensors"
   description: "This flag controls if the callback onDynamicSensorsDisconnected is implemented or not."
   bug: "316958439"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "sensor_event_connection_send_event_require_nonnull_scratch"
+  namespace: "sensors"
+  description: "This flag controls we allow to pass in nullptr as scratch in SensorEventConnection::sendEvents()"
+  bug: "339306599"
+}
+
+flag {
+  name: "sensor_service_clear_dynamic_sensor_data_at_the_end"
+  namespace: "sensors"
+  description: "When this flag is enabled, sensor service will only erase dynamic sensor data at the end of the threadLoop to prevent race condition."
+  bug: "329020894"
+}
diff --git a/services/stats/Android.bp b/services/stats/Android.bp
index 6b99627..f698515 100644
--- a/services/stats/Android.bp
+++ b/services/stats/Android.bp
@@ -7,6 +7,11 @@
     default_applicable_licenses: ["frameworks_native_license"],
 }
 
+vintf_fragment {
+    name: "android.frameworks.stats-service.xml",
+    src: "android.frameworks.stats-service.xml",
+}
+
 cc_library_shared {
     name: "libstatshidl",
     srcs: [
@@ -38,7 +43,7 @@
     local_include_dirs: [
         "include/stats",
     ],
-    vintf_fragments: [
+    vintf_fragment_modules: [
         "android.frameworks.stats-service.xml",
     ],
 }
diff --git a/services/stats/OWNERS b/services/stats/OWNERS
index a61babf..791b711 100644
--- a/services/stats/OWNERS
+++ b/services/stats/OWNERS
@@ -1,9 +1,7 @@
 jeffreyhuang@google.com
 joeo@google.com
-jtnguyen@google.com
 muhammadq@google.com
 ruchirr@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
-yro@google.com
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 46252e1..c2a9880 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -45,7 +45,9 @@
         "android.hardware.power-ndk_shared",
         "librenderengine_deps",
         "libtimestats_deps",
+        "libsurfaceflinger_common_deps",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_proto_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"SurfaceFlinger\"",
@@ -82,26 +84,24 @@
         "libprotobuf-cpp-lite",
         "libsync",
         "libui",
-        "libinput",
         "libutils",
         "libSurfaceFlingerProp",
-        "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
     ],
     static_libs: [
+        "iinputflinger_aidl_lib_static",
         "libaidlcommonsupport",
         "libcompositionengine",
         "libframetimeline",
         "libgui_aidl_static",
-        "liblayers_proto",
         "libperfetto_client_experimental",
         "librenderengine",
         "libscheduler",
         "libserviceutils",
         "libshaders",
-        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
         "libtimestats",
         "libtonemap",
-        "libsurfaceflingerflags",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
@@ -160,6 +160,7 @@
         "BackgroundExecutor.cpp",
         "Client.cpp",
         "ClientCache.cpp",
+        "Display/DisplayModeController.cpp",
         "Display/DisplaySnapshot.cpp",
         "DisplayDevice.cpp",
         "DisplayHardware/AidlComposerHal.cpp",
@@ -186,6 +187,7 @@
         "FrameTracker.cpp",
         "HdrLayerInfoReporter.cpp",
         "HdrSdrRatioOverlay.cpp",
+        "Jank/JankTracker.cpp",
         "WindowInfosListenerInvoker.cpp",
         "Layer.cpp",
         "LayerFE.cpp",
@@ -212,7 +214,6 @@
         "Scheduler/VsyncModulator.cpp",
         "Scheduler/VsyncSchedule.cpp",
         "ScreenCaptureOutput.cpp",
-        "StartPropertySetThread.cpp",
         "SurfaceFlinger.cpp",
         "SurfaceFlingerDefaultFactory.cpp",
         "Tracing/LayerDataSource.cpp",
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
index 5a1ec6f..3cef875 100644
--- a/services/surfaceflinger/BackgroundExecutor.cpp
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -19,6 +19,9 @@
 #define LOG_TAG "BackgroundExecutor"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <processgroup/sched_policy.h>
+#include <pthread.h>
+#include <sched.h>
 #include <utils/Log.h>
 #include <mutex>
 
@@ -26,14 +29,24 @@
 
 namespace android {
 
-ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
+namespace {
 
-BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+void set_thread_priority(bool highPriority) {
+    set_sched_policy(0, highPriority ? SP_FOREGROUND : SP_BACKGROUND);
+    struct sched_param param = {0};
+    param.sched_priority = highPriority ? 2 : 0 /* must be 0 for non-RT */;
+    sched_setscheduler(gettid(), highPriority ? SCHED_FIFO : SCHED_NORMAL, &param);
+}
+
+} // anonymous namespace
+
+BackgroundExecutor::BackgroundExecutor(bool highPriority) {
     // mSemaphore must be initialized before any calls to
     // BackgroundExecutor::sendCallbacks. For this reason, we initialize it
     // within the constructor instead of within mThread.
     LOG_ALWAYS_FATAL_IF(sem_init(&mSemaphore, 0, 0), "sem_init failed");
-    mThread = std::thread([&]() {
+    mThread = std::thread([&, highPriority]() {
+        set_thread_priority(highPriority);
         while (!mDone) {
             LOG_ALWAYS_FATAL_IF(sem_wait(&mSemaphore), "sem_wait failed (%d)", errno);
             auto callbacks = mCallbacksQueue.pop();
@@ -45,6 +58,11 @@
             }
         }
     });
+    if (highPriority) {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec HP");
+    } else {
+        pthread_setname_np(mThread.native_handle(), "BckgrndExec LP");
+    }
 }
 
 BackgroundExecutor::~BackgroundExecutor() {
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index 66b7d7a..1b5fadd 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -18,7 +18,6 @@
 
 #include <ftl/small_vector.h>
 #include <semaphore.h>
-#include <utils/Singleton.h>
 #include <thread>
 
 #include "LocklessQueue.h"
@@ -26,10 +25,20 @@
 namespace android {
 
 // Executes tasks off the main thread.
-class BackgroundExecutor : public Singleton<BackgroundExecutor> {
+class BackgroundExecutor {
 public:
-    BackgroundExecutor();
     ~BackgroundExecutor();
+
+    static BackgroundExecutor& getInstance() {
+        static BackgroundExecutor instance(true);
+        return instance;
+    }
+
+    static BackgroundExecutor& getLowPriorityInstance() {
+        static BackgroundExecutor instance(false);
+        return instance;
+    }
+
     using Callbacks = ftl::SmallVector<std::function<void()>, 10>;
     // Queues callbacks onto a work queue to be executed by a background thread.
     // This is safe to call from multiple threads.
@@ -37,6 +46,8 @@
     void flushQueue();
 
 private:
+    BackgroundExecutor(bool highPriority);
+
     sem_t mSemaphore;
     std::atomic_bool mDone = false;
 
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 6b4215e..77bf145 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -21,7 +21,7 @@
 
 #include <private/android_filesystem_config.h>
 
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/SchedulingPolicy.h>
 
 #include "Client.h"
@@ -53,7 +53,6 @@
                                      const sp<IBinder>& parent, const gui::LayerMetadata& metadata,
                                      gui::CreateSurfaceResult* outResult) {
     // We rely on createLayer to check permissions.
-    sp<IBinder> handle;
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), name.c_str(),
                            static_cast<uint32_t>(flags), std::move(metadata));
     args.parentHandle = parent;
@@ -101,7 +100,6 @@
 
 binder::Status Client::mirrorSurface(const sp<IBinder>& mirrorFromHandle,
                                      gui::CreateSurfaceResult* outResult) {
-    sp<IBinder> handle;
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), "MirrorRoot",
                            0 /* flags */, gui::LayerMetadata());
     status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, *outResult);
@@ -109,7 +107,6 @@
 }
 
 binder::Status Client::mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) {
-    sp<IBinder> handle;
     LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this),
                            "MirrorRoot-" + std::to_string(displayId), 0 /* flags */,
                            gui::LayerMetadata());
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index 09e41ff..40ea8d3 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -22,7 +22,7 @@
 #include <cinttypes>
 
 #include <android-base/stringprintf.h>
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <renderengine/impl/ExternalTexture.h>
 
 #include "ClientCache.h"
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index dad945c..141a228 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -17,6 +17,7 @@
         "librenderengine_deps",
         "libtimestats_deps",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_proto_deps",
     ],
     cflags: [
         "-DLOG_TAG=\"CompositionEngine\"",
@@ -39,19 +40,16 @@
         "libSurfaceFlingerProp",
         "libui",
         "libutils",
-        "server_configurable_flags",
     ],
     static_libs: [
-        "liblayers_proto",
         "libmath",
         "librenderengine",
         "libtimestats",
         "libtonemap",
         "libaidlcommonsupport",
         "libprocessgroup",
-        "libcgrouprc",
+        "libprocessgroup_util",
         "libjsoncpp",
-        "libcgrouprc_format",
     ],
     header_libs: [
         "android.hardware.graphics.composer@2.1-command-buffer",
@@ -91,24 +89,23 @@
 
 cc_library {
     name: "libcompositionengine",
-    defaults: ["libcompositionengine_defaults"],
-    static_libs: [
-        "libsurfaceflinger_common",
-        "libsurfaceflingerflags",
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_deps",
     ],
     srcs: [
         ":libcompositionengine_sources",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
-    shared_libs: [
-        "server_configurable_flags",
-    ],
 }
 
 cc_library {
     name: "libcompositionengine_mocks",
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "mock/CompositionEngine.cpp",
         "mock/Display.cpp",
@@ -124,11 +121,6 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
@@ -141,7 +133,10 @@
         "frameworks/native/services/surfaceflinger/common/include",
         "frameworks/native/services/surfaceflinger/tests/unittests",
     ],
-    defaults: ["libcompositionengine_defaults"],
+    defaults: [
+        "libcompositionengine_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         ":libcompositionengine_sources",
         "tests/planner/CachedSetTest.cpp",
@@ -152,6 +147,7 @@
         "tests/CompositionEngineTest.cpp",
         "tests/DisplayColorProfileTest.cpp",
         "tests/DisplayTest.cpp",
+        "tests/HwcAsyncWorkerTest.cpp",
         "tests/HwcBufferCacheTest.cpp",
         "tests/MockHWC2.cpp",
         "tests/MockHWComposer.cpp",
@@ -167,14 +163,11 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
-        "libsurfaceflinger_common_test",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         // For some reason, libvulkan isn't picked up from librenderengine
         // Probably ASAN related?
         "libvulkan",
-        "server_configurable_flags",
     ],
     sanitize: {
         hwaddress: true,
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 7c10fa5..e32cc02 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -73,6 +73,9 @@
     // TODO(b/121291683): These will become private/internal
     virtual void preComposition(CompositionRefreshArgs&) = 0;
 
+    // Resolves any unfulfilled promises for release fences
+    virtual void postComposition(CompositionRefreshArgs&) = 0;
+
     virtual FeatureFlags getFeatureFlags() const = 0;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
index 18a96f4..dd0f985 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -19,6 +19,7 @@
 #include <chrono>
 #include <optional>
 #include <vector>
+#include "utils/Timers.h"
 
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFE.h>
@@ -33,12 +34,6 @@
 using Layers = std::vector<sp<compositionengine::LayerFE>>;
 using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
 
-struct BorderRenderInfo {
-    float width = 0;
-    half4 color;
-    std::vector<int32_t> layerIds;
-};
-
 // Interface of composition engine power hint callback.
 struct ICEPowerCallback {
     virtual void notifyCpuLoadUp() = 0;
@@ -100,11 +95,12 @@
     // TODO (b/255601557): Calculate per display.
     std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
 
-    std::vector<BorderRenderInfo> borderInfoList;
-
     bool hasTrustedPresentationListener = false;
 
     ICEPowerCallback* powerCallback = nullptr;
+
+    // System time for when frame refresh starts. Used for stats.
+    nsecs_t refreshStartTime = 0;
 };
 
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index a1d6132..4e080b3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -58,8 +58,7 @@
     // Called before composition starts. Should return true if this layer has
     // pending updates which would require an extra display refresh cycle to
     // process.
-    virtual bool onPreComposition(nsecs_t refreshStartTime,
-                                  bool updatingOutputGeometryThisFrame) = 0;
+    virtual bool onPreComposition(bool updatingOutputGeometryThisFrame) = 0;
 
     struct ClientCompositionTargetSettings {
         enum class BlurSetting {
@@ -134,6 +133,15 @@
         uint64_t frameNumber = 0;
     };
 
+    // Describes the states of the release fence. Checking the states allows checks
+    // to ensure that set_value() is not called on the same promise multiple times,
+    // and can indicate if the promise has been fulfilled.
+    enum class ReleaseFencePromiseStatus {
+        UNINITIALIZED, // Promise not created
+        INITIALIZED,   // Promise created, fence has not been set
+        FULFILLED      // Promise fulfilled, fence is set
+    };
+
     // Returns the LayerSettings to pass to RenderEngine::drawLayers. The state may contain shadows
     // casted by the layer or the content of the layer itself. If the layer does not render then an
     // empty optional will be returned.
@@ -143,6 +151,19 @@
     // Called after the layer is displayed to update the presentation fence
     virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack) = 0;
 
+    // Initializes a promise for a buffer release fence and provides the future for that
+    // fence. This should only be called when a promise has not yet been created, or
+    // after the previous promise has already been fulfilled. Attempting to call this
+    // when an existing promise is INITIALIZED will fail because the promise has not
+    // yet been fulfilled.
+    virtual ftl::Future<FenceResult> createReleaseFenceFuture() = 0;
+
+    // Sets promise with its buffer's release fence
+    virtual void setReleaseFence(const FenceResult& releaseFence) = 0;
+
+    // Checks if the buffer's release fence has been set
+    virtual LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() = 0;
+
     // Gets some kind of identifier for the layer for debug purposes.
     virtual const char* getDebugName() const = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 11759b8..d1429a2 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -35,6 +35,7 @@
 #pragma clang diagnostic ignored "-Wextra"
 
 #include <gui/BufferQueue.h>
+#include <ui/EdgeExtensionEffect.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/GraphicTypes.h>
 #include <ui/StretchEffect.h>
@@ -133,12 +134,16 @@
     // The bounds of the layer in layer local coordinates
     FloatRect geomLayerBounds;
 
+    // The crop to apply to the layer in layer local coordinates
+    FloatRect geomLayerCrop;
+
     ShadowSettings shadowSettings;
 
     // List of regions that require blur
     std::vector<BlurRegion> blurRegions;
 
     StretchEffect stretchEffect;
+    EdgeExtensionEffect edgeExtensionEffect;
 
     /*
      * Geometry state
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index f1d6f52..191d475 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -306,7 +306,7 @@
     virtual void finishFrame(GpuCompositionResult&&) = 0;
     virtual std::optional<base::unique_fd> composeSurfaces(
             const Region&, std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
-    virtual void presentFrameAndReleaseLayers() = 0;
+    virtual void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) = 0;
     virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
     virtual bool chooseCompositionStrategy(
             std::optional<android::HWComposer::DeviceRequestedChanges>*) = 0;
@@ -314,6 +314,7 @@
             const std::optional<android::HWComposer::DeviceRequestedChanges>& changes) = 0;
     virtual bool getSkipColorTransform() const = 0;
     virtual FrameFences presentFrame() = 0;
+    virtual void executeCommands() = 0;
     virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
             bool supportsProtectedContent, ui::Dataspace outputDataspace,
             std::vector<LayerFE*> &outLayerRef) = 0;
@@ -321,8 +322,11 @@
             const Region& flashRegion,
             std::vector<LayerFE::LayerSettings>& clientCompositionLayers) = 0;
     virtual void setExpensiveRenderingExpected(bool enabled) = 0;
+    virtual void setHintSessionGpuStart(TimePoint startTime) = 0;
     virtual void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) = 0;
+    virtual void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) = 0;
     virtual bool isPowerHintSessionEnabled() = 0;
+    virtual bool isPowerHintSessionGpuReportingEnabled() = 0;
     virtual void cacheClientCompositionRequests(uint32_t cacheSize) = 0;
     virtual bool canPredictCompositionStrategy(const CompositionRefreshArgs&) = 0;
 };
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
index c699557..45208dd 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h
@@ -48,6 +48,8 @@
 
     void preComposition(CompositionRefreshArgs&) override;
 
+    void postComposition(CompositionRefreshArgs&) override;
+
     FeatureFlags getFeatureFlags() const override;
 
     // Debugging
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
index 2dc9a1a..d1eff24 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -60,6 +60,7 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override;
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentFrame() override;
+    void executeCommands() override;
     void setExpensiveRenderingExpected(bool) override;
     void finishFrame(GpuCompositionResult&&) override;
     bool supportsOffloadPresent() const override;
@@ -93,7 +94,10 @@
 
 private:
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     DisplayId mId;
     bool mIsDisconnected = false;
     Hwc2::PowerAdvisor* mPowerAdvisor = nullptr;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 911d67b..9990a74 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -104,7 +104,7 @@
     std::optional<base::unique_fd> composeSurfaces(const Region&,
                                                    std::shared_ptr<renderengine::ExternalTexture>,
                                                    base::unique_fd&) override;
-    void presentFrameAndReleaseLayers() override;
+    void presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) override;
     void renderCachedSets(const CompositionRefreshArgs&) override;
     void cacheClientCompositionRequests(uint32_t) override;
     bool canPredictCompositionStrategy(const CompositionRefreshArgs&) override;
@@ -123,7 +123,8 @@
     virtual std::future<bool> chooseCompositionStrategyAsync(
             std::optional<android::HWComposer::DeviceRequestedChanges>*);
     virtual void resetCompositionStrategy();
-    virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync();
+    virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync(
+            bool flushEvenWhenDisabled);
 
 protected:
     std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -137,6 +138,7 @@
     void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
     bool getSkipColorTransform() const override;
     compositionengine::Output::FrameFences presentFrame() override;
+    void executeCommands() override {}
     virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings(
             const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
     std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
@@ -144,8 +146,11 @@
             std::vector<LayerFE*>& outLayerFEs) override;
     void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
     void setExpensiveRenderingExpected(bool enabled) override;
+    void setHintSessionGpuStart(TimePoint startTime) override;
     void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
+    void setHintSessionRequiresRenderEngine(bool requiresRenderEngine) override;
     bool isPowerHintSessionEnabled() override;
+    bool isPowerHintSessionGpuReportingEnabled() override;
     void dumpBase(std::string&) const;
 
     // Implemented by the final implementation for the final state it uses.
@@ -162,7 +167,6 @@
 
 private:
     void dirtyEntireOutput();
-    void updateCompositionStateForBorder(const compositionengine::CompositionRefreshArgs&);
     compositionengine::OutputLayer* findLayerRequestingBackgroundComposition() const;
     void finishPrepareFrame();
     ui::Dataspace getBestDataspace(ui::Dataspace*, bool*) const;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 6b1c318..f8ffde1 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -34,7 +34,6 @@
 
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/ProjectionSpace.h>
-#include <renderengine/BorderRenderInfo.h>
 #include <ui/LayerStack.h>
 #include <ui/Rect.h>
 #include <ui/Region.h>
@@ -166,8 +165,6 @@
 
     bool treat170mAsSrgb = false;
 
-    std::vector<renderengine::BorderRenderInfo> borderInfoList;
-
     uint64_t lastOutputLayerHash = 0;
     uint64_t outputLayerHash = 0;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index e2d17ee..86bcf20 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -143,7 +143,7 @@
 
     compositionengine::OutputLayer* getBlurLayer() const;
 
-    bool hasUnsupportedDataspace() const;
+    bool hasKnownColorShift() const;
 
     bool hasProtectedLayers() const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index dc3821c..5e3e3d8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -74,6 +74,7 @@
     BlurRegions           = 1u << 18,
     HasProtectedContent   = 1u << 19,
     CachingHint           = 1u << 20,
+    DimmingEnabled        = 1u << 21,
 };
 // clang-format on
 
@@ -248,6 +249,10 @@
 
     ui::Dataspace getDataspace() const { return mOutputDataspace.get(); }
 
+    hardware::graphics::composer::hal::PixelFormat getPixelFormat() const {
+        return mPixelFormat.get();
+    }
+
     float getHdrSdrRatio() const {
         return getOutputLayer()->getLayerFE().getCompositionState()->currentHdrSdrRatio;
     };
@@ -258,6 +263,8 @@
 
     gui::CachingHint getCachingHint() const { return mCachingHint.get(); }
 
+    bool isDimmingEnabled() const { return mIsDimmingEnabled.get(); }
+
     float getFps() const { return getOutputLayer()->getLayerFE().getCompositionState()->fps; }
 
     void dump(std::string& result) const;
@@ -498,7 +505,10 @@
                              return std::vector<std::string>{toString(cachingHint)};
                          }};
 
-    static const constexpr size_t kNumNonUniqueFields = 19;
+    OutputLayerState<bool, LayerStateField::DimmingEnabled> mIsDimmingEnabled{
+            [](auto layer) { return layer->getLayerFE().getCompositionState()->dimmingEnabled; }};
+
+    static const constexpr size_t kNumNonUniqueFields = 20;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -516,7 +526,7 @@
                 &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
                 &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
                 &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
-                &mFrameNumber,  &mIsProtected,    &mCachingHint};
+                &mFrameNumber,  &mIsProtected,    &mCachingHint,          &mIsDimmingEnabled};
     }
 };
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
index 9b2387b..a1b7282 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h
@@ -52,6 +52,7 @@
     MOCK_METHOD1(updateCursorAsync, void(CompositionRefreshArgs&));
 
     MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+    MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
 
     MOCK_CONST_METHOD0(getFeatureFlags, FeatureFlags());
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 15e4577..05a5d38 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
@@ -20,6 +20,7 @@
 #include <compositionengine/LayerFECompositionState.h>
 #include <gmock/gmock.h>
 #include <ui/Fence.h>
+#include "ui/FenceResult.h"
 
 namespace android::compositionengine::mock {
 
@@ -43,7 +44,7 @@
 
     MOCK_CONST_METHOD0(getCompositionState, const LayerFECompositionState*());
 
-    MOCK_METHOD2(onPreComposition, bool(nsecs_t, bool));
+    MOCK_METHOD1(onPreComposition, bool(bool));
 
     MOCK_CONST_METHOD1(prepareClientComposition,
                        std::optional<compositionengine::LayerFE::LayerSettings>(
@@ -52,6 +53,9 @@
     MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, ui::LayerStack),
                 (override));
 
+    MOCK_METHOD0(createReleaseFenceFuture, ftl::Future<FenceResult>());
+    MOCK_METHOD1(setReleaseFence, void(const FenceResult&));
+    MOCK_METHOD0(getReleaseFencePromiseStatus, LayerFE::ReleaseFencePromiseStatus());
     MOCK_CONST_METHOD0(getDebugName, const char*());
     MOCK_CONST_METHOD0(getSequence, int32_t());
     MOCK_CONST_METHOD0(hasRoundedCorners, bool());
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 95ea3a4..d5bf2b5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -121,9 +121,10 @@
                                                 base::unique_fd&));
     MOCK_CONST_METHOD0(getSkipColorTransform, bool());
 
-    MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+    MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
     MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&));
     MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
+    MOCK_METHOD(void, executeCommands, ());
 
     MOCK_METHOD3(generateClientCompositionRequests,
                  std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<compositionengine::LayerFE*>&));
@@ -134,8 +135,11 @@
     MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
     MOCK_METHOD1(setPredictCompositionStrategy, void(bool));
     MOCK_METHOD1(setTreat170mAsSrgb, void(bool));
+    MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime));
     MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence));
+    MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine));
     MOCK_METHOD(bool, isPowerHintSessionEnabled, ());
+    MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, ());
 };
 
 } // namespace android::compositionengine::mock
diff --git a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
index bdaa1d0..d9018bc 100644
--- a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -37,7 +37,8 @@
             lhs.colorTransform == rhs.colorTransform &&
             lhs.disableBlending == rhs.disableBlending && lhs.shadow == rhs.shadow &&
             lhs.backgroundBlurRadius == rhs.backgroundBlurRadius &&
-            lhs.stretchEffect == rhs.stretchEffect;
+            lhs.stretchEffect == rhs.stretchEffect &&
+            lhs.edgeExtensionEffect == rhs.edgeExtensionEffect;
 }
 
 inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index d87eae3..5c5d0cd 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/trace.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/LayerFE.h>
 #include <compositionengine/LayerFECompositionState.h>
@@ -23,7 +24,6 @@
 #include <ui/DisplayMap.h>
 
 #include <renderengine/RenderEngine.h>
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -128,7 +128,7 @@
 } // namespace
 
 void CompositionEngine::present(CompositionRefreshArgs& args) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     preComposition(args);
@@ -155,13 +155,14 @@
     }
 
     {
-        ATRACE_NAME("Waiting on HWC");
+        SFTRACE_NAME("Waiting on HWC");
         for (auto& future : presentFutures) {
             // TODO(b/185536303): Call ftl::Future::wait() once it exists, since
             // we do not need the return value of get().
             future.get();
         }
     }
+    postComposition(args);
 }
 
 void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
@@ -176,15 +177,15 @@
 }
 
 void CompositionEngine::preComposition(CompositionRefreshArgs& args) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     bool needsAnotherUpdate = false;
 
-    mRefreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    mRefreshStartTime = args.refreshStartTime;
 
     for (auto& layer : args.layers) {
-        if (layer->onPreComposition(mRefreshStartTime, args.updatingOutputGeometryThisFrame)) {
+        if (layer->onPreComposition(args.updatingOutputGeometryThisFrame)) {
             needsAnotherUpdate = true;
         }
     }
@@ -192,6 +193,34 @@
     mNeedsAnotherUpdate = needsAnotherUpdate;
 }
 
+// If a buffer is latched but the layer is not presented, such as when
+// obscured by another layer, the previous buffer needs to be released. We find
+// these buffers and fire a NO_FENCE to release it. This ensures that all
+// promises for buffer releases are fulfilled at the end of composition.
+void CompositionEngine::postComposition(CompositionRefreshArgs& args) {
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        SFTRACE_CALL();
+        ALOGV(__FUNCTION__);
+
+        for (auto& layerFE : args.layers) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+
+        // List of layersWithQueuedFrames does not necessarily overlap with
+        // list of layers, so those layersWithQueuedFrames also need any
+        // unfulfilled promises to be resolved for completeness.
+        for (auto& layerFE : args.layersWithQueuedFrames) {
+            if (layerFE->getReleaseFencePromiseStatus() ==
+                LayerFE::ReleaseFencePromiseStatus::INITIALIZED) {
+                layerFE->setReleaseFence(Fence::NO_FENCE);
+            }
+        }
+    }
+}
+
 FeatureFlags CompositionEngine::getFeatureFlags() const {
     return {};
 }
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index 6428d08..77b1940 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -15,6 +15,7 @@
  */
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayCreationArgs.h>
@@ -25,9 +26,6 @@
 #include <compositionengine/impl/DumpHelpers.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/RenderSurface.h>
-#include <gui/TraceUtils.h>
-
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -79,7 +77,7 @@
 }
 
 bool Display::isVirtual() const {
-    return VirtualDisplayId::tryCast(mId).has_value();
+    return mId.isVirtual();
 }
 
 std::optional<DisplayId> Display::getDisplayId() const {
@@ -235,7 +233,7 @@
 
 bool Display::chooseCompositionStrategy(
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
-    ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
+    SFTRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
     ALOGV(__FUNCTION__);
 
     if (mIsDisconnected) {
@@ -252,10 +250,6 @@
     auto& hwc = getCompositionEngine().getHwComposer();
     const bool requiresClientComposition = anyLayersRequireClientComposition();
 
-    if (isPowerHintSessionEnabled()) {
-        mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
-    }
-
     const TimePoint hwcValidateStartTime = TimePoint::now();
 
     if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
@@ -365,6 +359,15 @@
             static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
 }
 
+void Display::executeCommands() {
+    const auto halDisplayIdOpt = HalDisplayId::tryCast(mId);
+    if (mIsDisconnected || !halDisplayIdOpt) {
+        return;
+    }
+
+    getCompositionEngine().getHwComposer().executeCommands(*halDisplayIdOpt);
+}
+
 compositionengine::Output::FrameFences Display::presentFrame() {
     auto fences = impl::Output::presentFrame();
 
@@ -416,10 +419,24 @@
     return mPowerAdvisor != nullptr && mPowerAdvisor->usePowerHintSession();
 }
 
+bool Display::isPowerHintSessionGpuReportingEnabled() {
+    return mPowerAdvisor != nullptr && mPowerAdvisor->supportsGpuReporting();
+}
+
+// For ADPF GPU v0 this is expected to set start time to when the GPU commands are submitted with
+// fence returned, i.e. when RenderEngine flushes the commands and returns the draw fence.
+void Display::setHintSessionGpuStart(TimePoint startTime) {
+    mPowerAdvisor->setGpuStartTime(mId, startTime);
+}
+
 void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) {
     mPowerAdvisor->setGpuFenceTime(mId, std::move(gpuFence));
 }
 
+void Display::setHintSessionRequiresRenderEngine(bool requiresRenderEngine) {
+    mPowerAdvisor->setRequiresRenderEngine(mId, requiresRenderEngine);
+}
+
 void Display::finishFrame(GpuCompositionResult&& result) {
     // We only need to actually compose the display if:
     // 1) It is being handled by hardware composer, which may need this to
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp b/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp
index 6086f0b..91385b4 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcAsyncWorker.cpp
@@ -24,6 +24,7 @@
 
 #include <android-base/thread_annotations.h>
 #include <cutils/sched_policy.h>
+#include <ftl/fake_guard.h>
 
 namespace android::compositionengine::impl {
 
@@ -60,7 +61,7 @@
     std::unique_lock<std::mutex> lock(mMutex);
     android::base::ScopedLockAssertion assumeLock(mMutex);
     while (!mDone) {
-        mCv.wait(lock);
+        mCv.wait(lock, [this]() FTL_FAKE_GUARD(mMutex) { return mTaskRequested || mDone; });
         if (mTaskRequested && mTask.valid()) {
             mTask();
             mTaskRequested = false;
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index 921e05d..2d8f98f 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -16,6 +16,8 @@
 
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -30,7 +32,6 @@
 #include <compositionengine/impl/planner/Planner.h>
 #include <ftl/algorithm.h>
 #include <ftl/future.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/Time.h>
 
@@ -52,7 +53,6 @@
 #include <android-base/properties.h>
 #include <ui/DebugUtils.h>
 #include <ui/HdrCapabilities.h>
-#include <utils/Trace.h>
 
 #include "TracedOrdinal.h"
 
@@ -423,7 +423,7 @@
 
 void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArgs,
                      LayerFESet& geomSnapshots) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     rebuildLayerStacks(refreshArgs, geomSnapshots);
@@ -452,8 +452,8 @@
                 })
                 .value();
     };
-    ATRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
-                  stringifyExpectedPresentTime().c_str());
+    SFTRACE_FORMAT("%s for %s%s", __func__, mNamePlusId.c_str(),
+                   stringifyExpectedPresentTime().c_str());
     ALOGV(__FUNCTION__);
 
     updateColorProfile(refreshArgs);
@@ -463,6 +463,10 @@
     setColorTransform(refreshArgs);
     beginFrame();
 
+    if (isPowerHintSessionEnabled()) {
+        // always reset the flag before the composition prediction
+        setHintSessionRequiresRenderEngine(false);
+    }
     GpuCompositionResult result;
     const bool predictCompositionStrategy = canPredictCompositionStrategy(refreshArgs);
     if (predictCompositionStrategy) {
@@ -474,8 +478,9 @@
     devOptRepaintFlash(refreshArgs);
     finishFrame(std::move(result));
     ftl::Future<std::monostate> future;
+    const bool flushEvenWhenDisabled = !refreshArgs.bufferIdsToUncache.empty();
     if (mOffloadPresent) {
-        future = presentFrameAndReleaseLayersAsync();
+        future = presentFrameAndReleaseLayersAsync(flushEvenWhenDisabled);
 
         // Only offload for this frame. The next frame will determine whether it
         // needs to be offloaded. Leave the HwcAsyncWorker in place. For one thing,
@@ -483,7 +488,7 @@
         // we don't want to churn.
         mOffloadPresent = false;
     } else {
-        presentFrameAndReleaseLayers();
+        presentFrameAndReleaseLayers(flushEvenWhenDisabled);
         future = ftl::yield<std::monostate>({});
     }
     renderCachedSets(refreshArgs);
@@ -512,7 +517,7 @@
     if (!outputState.isEnabled || CC_LIKELY(!refreshArgs.updatingOutputGeometryThisFrame)) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     // Process the layers to determine visibility and coverage
@@ -798,7 +803,7 @@
 }
 
 void Output::updateCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
@@ -818,44 +823,6 @@
             forceClientComposition = false;
         }
     }
-
-    updateCompositionStateForBorder(refreshArgs);
-}
-
-void Output::updateCompositionStateForBorder(
-        const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    std::unordered_map<int32_t, const Region*> layerVisibleRegionMap;
-    // Store a map of layerId to their computed visible region.
-    for (auto* layer : getOutputLayersOrderedByZ()) {
-        int layerId = (layer->getLayerFE()).getSequence();
-        layerVisibleRegionMap[layerId] = &((layer->getState()).visibleRegion);
-    }
-    OutputCompositionState& outputCompositionState = editState();
-    outputCompositionState.borderInfoList.clear();
-    bool clientComposeTopLayer = false;
-    for (const auto& borderInfo : refreshArgs.borderInfoList) {
-        renderengine::BorderRenderInfo info;
-        for (const auto& id : borderInfo.layerIds) {
-            info.combinedRegion.orSelf(*(layerVisibleRegionMap[id]));
-        }
-
-        if (!info.combinedRegion.isEmpty()) {
-            info.width = borderInfo.width;
-            info.color = borderInfo.color;
-            outputCompositionState.borderInfoList.emplace_back(std::move(info));
-            clientComposeTopLayer = true;
-        }
-    }
-
-    // In this situation we must client compose the top layer instead of using hwc
-    // because we want to draw the border above all else.
-    // This could potentially cause a bit of a performance regression if the top
-    // layer would have been rendered using hwc originally.
-    // TODO(b/227656283): Measure system's performance before enabling the border feature
-    if (clientComposeTopLayer) {
-        auto topLayer = getOutputLayerOrderedByZByIndex(getOutputLayerCount() - 1);
-        (topLayer->editState()).forceClientComposition = true;
-    }
 }
 
 void Output::planComposition() {
@@ -863,14 +830,14 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     mPlanner->plan(getOutputLayersOrderedByZ());
 }
 
 void Output::writeCompositionState(const compositionengine::CompositionRefreshArgs& refreshArgs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
@@ -1113,7 +1080,7 @@
 }
 
 void Output::prepareFrame() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     auto& outputState = editState();
@@ -1133,11 +1100,11 @@
     finishPrepareFrame();
 }
 
-ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync() {
-    return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
-               presentFrameAndReleaseLayers();
+ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync(bool flushEvenWhenDisabled) {
+    return ftl::Future<bool>(mHwComposerAsyncWorker->send([this, flushEvenWhenDisabled]() {
+               presentFrameAndReleaseLayers(flushEvenWhenDisabled);
                return true;
-           })))
+           }))
             .then([](bool) { return std::monostate{}; });
 }
 
@@ -1148,7 +1115,7 @@
 }
 
 GpuCompositionResult Output::prepareFrameAsync() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     auto& state = editState();
     const auto& previousChanges = state.previousDeviceRequestedChanges;
@@ -1178,7 +1145,7 @@
     state.strategyPrediction = predictionSucceeded ? CompositionStrategyPredictionState::SUCCESS
                                                    : CompositionStrategyPredictionState::FAIL;
     if (!predictionSucceeded) {
-        ATRACE_NAME("CompositionStrategyPredictionMiss");
+        SFTRACE_NAME("CompositionStrategyPredictionMiss");
         resetCompositionStrategy();
         if (chooseCompositionSuccess) {
             applyCompositionStrategy(changes);
@@ -1187,7 +1154,7 @@
         // Track the dequeued buffer to reuse so we don't need to dequeue another one.
         compositionResult.buffer = buffer;
     } else {
-        ATRACE_NAME("CompositionStrategyPredictionHit");
+        SFTRACE_NAME("CompositionStrategyPredictionHit");
     }
     state.previousDeviceRequestedChanges = std::move(changes);
     state.previousDeviceRequestedSuccess = chooseCompositionSuccess;
@@ -1210,7 +1177,8 @@
         }
     }
 
-    presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 
     std::this_thread::sleep_for(*refreshArgs.devOptFlashDirtyRegionsDelay);
 
@@ -1218,7 +1186,7 @@
 }
 
 void Output::finishFrame(GpuCompositionResult&& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
     const auto& outputState = getState();
     if (!outputState.isEnabled) {
@@ -1247,8 +1215,7 @@
     if (!optReadyFence) {
         return;
     }
-
-    if (isPowerHintSessionEnabled()) {
+    if (isPowerHintSessionEnabled() && !isPowerHintSessionGpuReportingEnabled()) {
         // get fence end time to know when gpu is complete in display
         setHintSessionGpuFence(
                 std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
@@ -1308,7 +1275,7 @@
 std::optional<base::unique_fd> Output::composeSurfaces(
         const Region& debugRegion, std::shared_ptr<renderengine::ExternalTexture> tex,
         base::unique_fd& fd) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV(__FUNCTION__);
 
     const auto& outputState = getState();
@@ -1349,13 +1316,13 @@
         if (mClientCompositionRequestCache->exists(tex->getBuffer()->getId(),
                                                    clientCompositionDisplay,
                                                    clientCompositionLayers)) {
-            ATRACE_NAME("ClientCompositionCacheHit");
+            SFTRACE_NAME("ClientCompositionCacheHit");
             outputCompositionState.reusedClientComposition = true;
             setExpensiveRenderingExpected(false);
             // b/239944175 pass the fence associated with the buffer.
             return base::unique_fd(std::move(fd));
         }
-        ATRACE_NAME("ClientCompositionCacheMiss");
+        SFTRACE_NAME("ClientCompositionCacheMiss");
         mClientCompositionRequestCache->add(tex->getBuffer()->getId(), clientCompositionDisplay,
                                             clientCompositionLayers);
     }
@@ -1392,8 +1359,20 @@
         // If rendering was not successful, remove the request from the cache.
         mClientCompositionRequestCache->remove(tex->getBuffer()->getId());
     }
-
     const auto fence = std::move(fenceResult).value_or(Fence::NO_FENCE);
+    if (isPowerHintSessionEnabled()) {
+        if (fence != Fence::NO_FENCE && fence->isValid() &&
+            !outputCompositionState.reusedClientComposition) {
+            setHintSessionRequiresRenderEngine(true);
+            if (isPowerHintSessionGpuReportingEnabled()) {
+                // the order of the two calls here matters as we should check if the previously
+                // tracked fence has signaled first and archive the previous start time
+                setHintSessionGpuStart(TimePoint::now());
+                setHintSessionGpuFence(
+                        std::make_unique<FenceTime>(sp<Fence>::make(dup(fence->get()))));
+            }
+        }
+    }
 
     if (auto timeStats = getCompositionEngine().getTimeStats()) {
         if (fence->isValid()) {
@@ -1443,13 +1422,6 @@
 
     // Compute the global color transform matrix.
     clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
-    for (auto& info : outputState.borderInfoList) {
-        renderengine::BorderRenderInfo borderInfo;
-        borderInfo.width = info.width;
-        borderInfo.color = info.color;
-        borderInfo.combinedRegion = info.combinedRegion;
-        clientCompositionDisplay.borderInfoList.emplace_back(std::move(borderInfo));
-    }
     clientCompositionDisplay.deviceHandlesColorTransform =
             outputState.usesDeviceComposition || getSkipColorTransform();
     return clientCompositionDisplay;
@@ -1576,19 +1548,36 @@
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionGpuStart(TimePoint) {
+    // The base class does nothing with this call.
+}
+
 void Output::setHintSessionGpuFence(std::unique_ptr<FenceTime>&&) {
     // The base class does nothing with this call.
 }
 
+void Output::setHintSessionRequiresRenderEngine(bool) {
+    // The base class does nothing with this call.
+}
+
 bool Output::isPowerHintSessionEnabled() {
     return false;
 }
 
-void Output::presentFrameAndReleaseLayers() {
-    ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
+bool Output::isPowerHintSessionGpuReportingEnabled() {
+    return false;
+}
+
+void Output::presentFrameAndReleaseLayers(bool flushEvenWhenDisabled) {
+    SFTRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
     ALOGV(__FUNCTION__);
 
     if (!getState().isEnabled) {
+        if (flushEvenWhenDisabled && FlagManager::getInstance().flush_buffer_slots_to_uncache()) {
+            // Some commands, like clearing buffer slots, should still be executed
+            // even if the display is not enabled.
+            executeCommands();
+        }
         return;
     }
 
@@ -1621,9 +1610,13 @@
             releaseFence =
                     Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
         }
-        layer->getLayerFE()
-                .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
-                                  outputState.layerFilter.layerStack);
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            layer->getLayerFE().setReleaseFence(releaseFence);
+        } else {
+            layer->getLayerFE()
+                    .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+                                      outputState.layerFilter.layerStack);
+        }
     }
 
     // We've got a list of layers needing fences, that are disjoint with
@@ -1631,8 +1624,12 @@
     // supply them with the present fence.
     for (auto& weakLayer : mReleasedLayers) {
         if (const auto layer = weakLayer.promote()) {
-            layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
-                                    outputState.layerFilter.layerStack);
+            if (FlagManager::getInstance().ce_fence_promise()) {
+                layer->setReleaseFence(frame.presentFence);
+            } else {
+                layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+                                        outputState.layerFilter.layerStack);
+            }
         }
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 39cf671..6683e67 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -67,11 +67,6 @@
 
     out.append("\n   ");
     dumpVal(out, "treat170mAsSrgb", treat170mAsSrgb);
-
-    out.append("\n");
-    for (const auto& borderRenderInfo : borderInfoList) {
-        dumpVal(out, "borderRegion", borderRenderInfo.combinedRegion);
-    }
     out.append("\n");
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
index c0b23d9..d6028bf 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/stringprintf.h>
 #include <android/native_window.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplaySurface.h>
@@ -32,7 +33,6 @@
 #include <system/window.h>
 #include <ui/GraphicBuffer.h>
 #include <ui/Rect.h>
-#include <utils/Trace.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
@@ -149,7 +149,7 @@
 
 std::shared_ptr<renderengine::ExternalTexture> RenderSurface::dequeueBuffer(
         base::unique_fd* bufferFence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     int fd = -1;
     ANativeWindowBuffer* buffer = nullptr;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 1f53588..409a206 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -21,15 +21,14 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <math/HashCombine.h>
 #include <renderengine/DisplaySettings.h>
 #include <renderengine/RenderEngine.h>
 #include <ui/DebugUtils.h>
-#include <utils/Trace.h>
-
-#include <utils/Trace.h>
+#include <ui/HdrRenderTypeUtils.h>
 
 namespace android::compositionengine::impl::planner {
 
@@ -161,7 +160,7 @@
 void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& texturePool,
                        const OutputCompositionState& outputState,
                        bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (outputState.powerCallback) {
         outputState.powerCallback->notifyCpuLoadUp();
     }
@@ -306,7 +305,7 @@
         return false;
     }
 
-    if (hasUnsupportedDataspace()) {
+    if (hasKnownColorShift()) {
         return false;
     }
 
@@ -366,12 +365,21 @@
     return mBlurLayer ? mBlurLayer->getOutputLayer() : nullptr;
 }
 
-bool CachedSet::hasUnsupportedDataspace() const {
+bool CachedSet::hasKnownColorShift() const {
     return std::any_of(mLayers.cbegin(), mLayers.cend(), [](const Layer& layer) {
         auto dataspace = layer.getState()->getDataspace();
-        const auto transfer = static_cast<ui::Dataspace>(dataspace & ui::Dataspace::TRANSFER_MASK);
-        if (transfer == ui::Dataspace::TRANSFER_ST2084 || transfer == ui::Dataspace::TRANSFER_HLG) {
-            // Skip HDR.
+
+        // Layers are never dimmed when rendering a cached set, meaning that we may ask HWC to
+        // dim a cached set. But this means that we can never cache any HDR layers so that we
+        // don't accidentally dim those layers.
+        const auto hdrType = getHdrRenderType(dataspace, layer.getState()->getPixelFormat(),
+                                              layer.getState()->getHdrSdrRatio());
+        if (hdrType != HdrRenderType::SDR) {
+            return true;
+        }
+
+        // Layers that have dimming disabled pretend that they're HDR.
+        if (!layer.getState()->isDimmingEnabled()) {
             return true;
         }
 
@@ -380,10 +388,6 @@
             // 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/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 0a5c43a..783209c 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -21,11 +21,10 @@
 
 #include <android-base/properties.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/impl/planner/Flattener.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
-#include <gui/TraceUtils.h>
-
 using time_point = std::chrono::steady_clock::time_point;
 using namespace std::chrono_literals;
 
@@ -77,7 +76,7 @@
 
 NonBufferHash Flattener::flattenLayers(const std::vector<const LayerState*>& layers,
                                        NonBufferHash hash, time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const size_t unflattenedDisplayCost = calculateDisplayCost(layers);
     mUnflattenedDisplayCost += unflattenedDisplayCost;
 
@@ -113,7 +112,7 @@
         const OutputCompositionState& outputState,
         std::optional<std::chrono::steady_clock::time_point> renderDeadline,
         bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!mNewCachedSet) {
         return;
@@ -121,7 +120,7 @@
 
     // Ensure that a cached set has a valid buffer first
     if (mNewCachedSet->hasRenderedBuffer()) {
-        ATRACE_NAME("mNewCachedSet->hasRenderedBuffer()");
+        SFTRACE_NAME("mNewCachedSet->hasRenderedBuffer()");
         return;
     }
 
@@ -138,13 +137,13 @@
 
             if (mNewCachedSet->getSkipCount() <=
                 mTunables.mRenderScheduling->maxDeferRenderAttempts) {
-                ATRACE_FORMAT("DeadlinePassed: exceeded deadline by: %d us",
-                              std::chrono::duration_cast<std::chrono::microseconds>(
-                                      estimatedRenderFinish - *renderDeadline)
-                                      .count());
+                SFTRACE_FORMAT("DeadlinePassed: exceeded deadline by: %d us",
+                               std::chrono::duration_cast<std::chrono::microseconds>(
+                                       estimatedRenderFinish - *renderDeadline)
+                                       .count());
                 return;
             } else {
-                ATRACE_NAME("DeadlinePassed: exceeded max skips");
+                SFTRACE_NAME("DeadlinePassed: exceeded max skips");
             }
         }
     }
@@ -271,7 +270,7 @@
 // was already populated with these layers, i.e. on the second and following
 // calls with the same geometry.
 bool Flattener::mergeWithCachedSets(const std::vector<const LayerState*>& layers, time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<CachedSet> merged;
 
     if (mLayers.empty()) {
@@ -415,7 +414,7 @@
 }
 
 std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<Run> runs;
     bool isPartOfRun = false;
     Run::Builder builder;
@@ -431,15 +430,15 @@
         if (!layerIsInactive && currentSet->getLayerCount() == kNumLayersFpsConsideration) {
             auto layerFps = currentSet->getFirstLayer().getState()->getFps();
             if (layerFps > 0 && layerFps <= kFpsActiveThreshold) {
-                ATRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
-                              currentSet->getFirstLayer().getName().c_str(), layerFps);
+                SFTRACE_FORMAT("layer is considered inactive due to low FPS [%s] %f",
+                               currentSet->getFirstLayer().getName().c_str(), layerFps);
                 layerIsInactive = true;
             }
         }
 
         if (!layerDeniedFromCaching && layerIsInactive &&
             (firstLayer || runHasFirstLayer || !layerHasBlur) &&
-            !currentSet->hasUnsupportedDataspace()) {
+            !currentSet->hasKnownColorShift()) {
             if (isPartOfRun) {
                 builder.increment();
             } else {
@@ -494,7 +493,7 @@
 }
 
 void Flattener::buildCachedSets(time_point now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mLayers.empty()) {
         ALOGV("[%s] No layers found, returning", __func__);
         return;
@@ -508,7 +507,7 @@
     for (const CachedSet& layer : mLayers) {
         // TODO (b/191997217): make it less aggressive, and sync with findCandidateRuns
         if (layer.hasProtectedLayers()) {
-            ATRACE_NAME("layer->hasProtectedLayers()");
+            SFTRACE_NAME("layer->hasProtectedLayers()");
             return;
         }
     }
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index 0e3fdbb..10dc927 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <common/FlagManager.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
 namespace {
@@ -70,6 +71,10 @@
         if (field->getField() == LayerStateField::Buffer) {
             continue;
         }
+        if (FlagManager::getInstance().cache_when_source_crop_layer_only_moved() &&
+            field->getField() == LayerStateField::SourceCrop) {
+            continue;
+        }
         android::hashCombineSingleHashed(hash, field->getHash());
     }
 
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 5e6cade..d114ff7 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -21,11 +21,11 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <compositionengine/LayerFECompositionState.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <compositionengine/impl/planner/Planner.h>
 
-#include <utils/Trace.h>
 #include <chrono>
 
 namespace android::compositionengine::impl::planner {
@@ -83,7 +83,7 @@
 
 void Planner::plan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::unordered_set<LayerId> removedLayers;
     removedLayers.reserve(mPreviousLayers.size());
 
@@ -165,7 +165,7 @@
 
 void Planner::reportFinalPlan(
         compositionengine::Output::OutputLayersEnumerator<compositionengine::Output>&& layers) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (!mPredictorEnabled) {
         return;
     }
@@ -204,7 +204,7 @@
 void Planner::renderCachedSets(const OutputCompositionState& outputState,
                                std::optional<std::chrono::steady_clock::time_point> renderDeadline,
                                bool deviceHandlesColorTransform) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mFlattener.renderCachedSets(outputState, renderDeadline, deviceHandlesColorTransform);
 }
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
index da578e2..639164d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -28,6 +28,7 @@
 
 #include "MockHWComposer.h"
 #include "TimeStats/TimeStats.h"
+#include "gmock/gmock.h"
 
 #include <variant>
 
@@ -90,14 +91,16 @@
         // These are the overridable functions CompositionEngine::present() may
         // call, and have separate test coverage.
         MOCK_METHOD1(preComposition, void(CompositionRefreshArgs&));
+        MOCK_METHOD1(postComposition, void(CompositionRefreshArgs&));
     };
 
     StrictMock<CompositionEnginePartialMock> mEngine;
 };
 
 TEST_F(CompositionEnginePresentTest, worksWithEmptyRequest) {
-    // present() always calls preComposition()
+    // present() always calls preComposition() and postComposition()
     EXPECT_CALL(mEngine, preComposition(Ref(mRefreshArgs)));
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
 
     mEngine.present(mRefreshArgs);
 }
@@ -126,6 +129,9 @@
     EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
 
+    // present() always calls postComposition()
+    EXPECT_CALL(mEngine, postComposition(Ref(mRefreshArgs)));
+
     mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
     mEngine.present(mRefreshArgs);
 }
@@ -214,6 +220,7 @@
 
 TEST_F(CompositionTestPreComposition, preCompositionSetsFrameTimestamp) {
     const nsecs_t before = systemTime(SYSTEM_TIME_MONOTONIC);
+    mRefreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
     mEngine.preComposition(mRefreshArgs);
     const nsecs_t after = systemTime(SYSTEM_TIME_MONOTONIC);
 
@@ -226,12 +233,9 @@
     nsecs_t ts1 = 0;
     nsecs_t ts2 = 0;
     nsecs_t ts3 = 0;
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _))
-            .WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts1), Return(false)));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts2), Return(false)));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(DoAll(SaveArg<0>(&ts3), Return(false)));
 
     mRefreshArgs.outputs = {mOutput1};
     mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
@@ -245,9 +249,9 @@
 }
 
 TEST_F(CompositionTestPreComposition, preCompositionDefaultsToNoUpdateNeeded) {
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
 
     mEngine.setNeedsAnotherUpdateForTest(true);
 
@@ -262,9 +266,9 @@
 
 TEST_F(CompositionTestPreComposition,
        preCompositionSetsNeedsAnotherUpdateIfAtLeastOneLayerRequestsIt) {
-    EXPECT_CALL(*mLayer1FE, onPreComposition(_, _)).WillOnce(Return(true));
-    EXPECT_CALL(*mLayer2FE, onPreComposition(_, _)).WillOnce(Return(false));
-    EXPECT_CALL(*mLayer3FE, onPreComposition(_, _)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer1FE, onPreComposition(_)).WillOnce(Return(true));
+    EXPECT_CALL(*mLayer2FE, onPreComposition(_)).WillOnce(Return(false));
+    EXPECT_CALL(*mLayer3FE, onPreComposition(_)).WillOnce(Return(false));
 
     mRefreshArgs.outputs = {mOutput1};
     mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
@@ -483,5 +487,29 @@
     mEngine.present(mRefreshArgs);
 }
 
+struct CompositionEnginePostCompositionTest : public CompositionEngineTest {
+    sp<StrictMock<mock::LayerFE>> mLayer1FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer2FE = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> mLayer3FE = sp<StrictMock<mock::LayerFE>>::make();
+};
+
+TEST_F(CompositionEnginePostCompositionTest, postCompositionReleasesAllFences) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    EXPECT_CALL(*mLayer1FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer2FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::FULFILLED));
+    EXPECT_CALL(*mLayer3FE, getReleaseFencePromiseStatus)
+            .WillOnce(Return(LayerFE::ReleaseFencePromiseStatus::INITIALIZED));
+    mRefreshArgs.layers = {mLayer1FE, mLayer2FE, mLayer3FE};
+
+    EXPECT_CALL(*mLayer1FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer2FE, setReleaseFence(_)).Times(0);
+    EXPECT_CALL(*mLayer3FE, setReleaseFence(_)).Times(1);
+
+    mEngine.postComposition(mRefreshArgs);
+}
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index a95a5c6..39163ea 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
@@ -1067,8 +1067,8 @@
 
     EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
     EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
-
-    mDisplay->presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mDisplay->presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 } // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcAsyncWorkerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcAsyncWorkerTest.cpp
new file mode 100644
index 0000000..dd04df6
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/tests/HwcAsyncWorkerTest.cpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2024 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 <future>
+
+#include <compositionengine/impl/HwcAsyncWorker.h>
+#include <gtest/gtest.h>
+
+namespace android::compositionengine {
+namespace {
+
+using namespace std::chrono_literals;
+
+// For the edge case tests below, how much real time should be spent trying to reproduce edge cases
+// problems in a loop.
+//
+// Larger values mean problems are more likely to be detected, at the cost of making the unit test
+// run slower.
+//
+// As we expect the tests to be run continuously, even a short loop will eventually catch
+// problems, though not necessarily from changes in the same build that introduce them.
+constexpr auto kWallTimeForEdgeCaseTests = 5ms;
+
+TEST(HwcAsyncWorker, continuousTasksEdgeCase) {
+    // Ensures that a single worker that is given multiple tasks in short succession will run them.
+
+    impl::HwcAsyncWorker worker;
+    const auto endTime = std::chrono::steady_clock::now() + kWallTimeForEdgeCaseTests;
+    while (std::chrono::steady_clock::now() < endTime) {
+        auto f1 = worker.send([] { return false; });
+        EXPECT_FALSE(f1.get());
+        auto f2 = worker.send([] { return true; });
+        EXPECT_TRUE(f2.get());
+    }
+}
+
+TEST(HwcAsyncWorker, constructAndDestroyEdgeCase) {
+    // Ensures that newly created HwcAsyncWorkers can be immediately destroyed.
+
+    const auto endTime = std::chrono::steady_clock::now() + kWallTimeForEdgeCaseTests;
+    while (std::chrono::steady_clock::now() < endTime) {
+        impl::HwcAsyncWorker worker;
+    }
+}
+
+TEST(HwcAsyncWorker, newlyCreatedRunsTasksEdgeCase) {
+    // Ensures that newly created HwcAsyncWorkers will run a task if given one immediately.
+
+    const auto endTime = std::chrono::steady_clock::now() + kWallTimeForEdgeCaseTests;
+    while (std::chrono::steady_clock::now() < endTime) {
+        impl::HwcAsyncWorker worker;
+        auto f = worker.send([] { return true; });
+        f.get();
+    }
+}
+
+} // namespace
+} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
index b0b1a02..eb6e677 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWC2.h
@@ -33,6 +33,7 @@
 #include "DisplayHardware/HWC2.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <aidl/android/hardware/graphics/composer3/Lut.h>
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
@@ -77,6 +78,7 @@
                  Error(const std::string&, bool, const std::vector<uint8_t>&));
     MOCK_METHOD1(setBrightness, Error(float));
     MOCK_METHOD1(setBlockingRegion, Error(const android::Region&));
+    MOCK_METHOD(Error, setLuts, (std::vector<aidl::android::hardware::graphics::composer3::Lut>&));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 575a30e..e910c72 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -51,7 +51,8 @@
     MOCK_CONST_METHOD0(getMaxVirtualDisplayCount, size_t());
     MOCK_CONST_METHOD0(getMaxVirtualDisplayDimension, size_t());
     MOCK_METHOD3(allocateVirtualDisplay, bool(HalVirtualDisplayId, ui::Size, ui::PixelFormat*));
-    MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
+    MOCK_METHOD3(allocatePhysicalDisplay,
+                 void(hal::HWDisplayId, PhysicalDisplayId, std::optional<ui::Size>));
 
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
     MOCK_METHOD(status_t, getDeviceCompositionChanges,
@@ -63,6 +64,7 @@
                 (override));
     MOCK_METHOD2(presentAndGetReleaseFences,
                  status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
+    MOCK_METHOD(status_t, executeCommands, (HalDisplayId));
     MOCK_METHOD2(setPowerMode, status_t(PhysicalDisplayId, hal::PowerMode));
     MOCK_METHOD2(setActiveConfig, status_t(HalDisplayId, size_t));
     MOCK_METHOD2(setColorTransform, status_t(HalDisplayId, const mat4&));
@@ -126,6 +128,7 @@
                        const std::unordered_map<std::string, bool>&());
 
     MOCK_CONST_METHOD1(dump, void(std::string&));
+    MOCK_CONST_METHOD1(dumpOverlayProperties, void(std::string&));
     MOCK_CONST_METHOD0(getComposer, android::Hwc2::Composer*());
 
     MOCK_METHOD(hal::HWDisplayId, getPrimaryHwcDisplayId, (), (const, override));
@@ -149,6 +152,10 @@
                 getOverlaySupport, (), (const, override));
     MOCK_METHOD(status_t, setRefreshRateChangedCallbackDebugEnabled, (PhysicalDisplayId, bool));
     MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
+    MOCK_METHOD(status_t, getRequestedLuts,
+                (PhysicalDisplayId,
+                 std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*),
+                (override));
 };
 
 } // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index 7253354..ed2ffa9 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
@@ -38,10 +38,12 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -51,8 +53,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index 799c7ed..c34168d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -2014,11 +2014,20 @@
         MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult());
         MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
         MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
         MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
+    OutputPresentTest() {
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
+    }
+
     StrictMock<OutputPartialMock> mOutput;
 };
 
@@ -2032,11 +2041,12 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(false));
     EXPECT_CALL(mOutput, prepareFrame());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false));
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2052,11 +2062,12 @@
     EXPECT_CALL(mOutput, writeCompositionState(Ref(args)));
     EXPECT_CALL(mOutput, setColorTransform(Ref(args)));
     EXPECT_CALL(mOutput, beginFrame());
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(false));
     EXPECT_CALL(mOutput, canPredictCompositionStrategy(Ref(args))).WillOnce(Return(true));
     EXPECT_CALL(mOutput, prepareFrameAsync());
     EXPECT_CALL(mOutput, devOptRepaintFlash(Ref(args)));
     EXPECT_CALL(mOutput, finishFrame(_));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(false));
     EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
 
     mOutput.present(args);
@@ -2902,7 +2913,7 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
         MOCK_METHOD0(prepareFrame, void());
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
@@ -2939,7 +2950,8 @@
     mOutput.mState.isEnabled = false;
 
     InSequence seq;
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2951,7 +2963,8 @@
 
     InSequence seq;
     EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2967,7 +2980,8 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _));
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
-    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
     EXPECT_CALL(mOutput, prepareFrame());
 
     mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -2985,10 +2999,14 @@
                      std::optional<base::unique_fd>(const Region&,
                                                     std::shared_ptr<renderengine::ExternalTexture>,
                                                     base::unique_fd&));
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
         MOCK_METHOD0(updateProtectedContentState, void());
         MOCK_METHOD2(dequeueRenderBuffer,
                      bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
+        MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputFinishFrameTest() {
@@ -2997,6 +3015,8 @@
         mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
         EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
         EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     StrictMock<OutputPartialMock> mOutput;
@@ -3023,6 +3043,22 @@
     mOutput.finishFrame(std::move(result));
 }
 
+TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFenceWithAdpfGpuOff) {
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
+    mOutput.mState.isEnabled = true;
+
+    InSequence seq;
+    EXPECT_CALL(mOutput, updateProtectedContentState());
+    EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
+    EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+            .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
+    EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+
+    impl::GpuCompositionResult result;
+    mOutput.finishFrame(std::move(result));
+}
+
 TEST_F(OutputFinishFrameTest, queuesBufferIfComposeSurfacesReturnsAFence) {
     mOutput.mState.isEnabled = true;
 
@@ -3031,6 +3067,7 @@
     EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _)).WillOnce(Return(true));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3058,6 +3095,7 @@
             .WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
     EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
 
     impl::GpuCompositionResult result;
@@ -3068,6 +3106,7 @@
     mOutput.mState.isEnabled = true;
     mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
     InSequence seq;
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
 
     impl::GpuCompositionResult result;
@@ -3090,6 +3129,7 @@
                 composeSurfaces(RegionEq(Region::INVALID_REGION), result.buffer,
                                 Eq(ByRef(result.fence))))
             .WillOnce(Return(ByMove(base::unique_fd())));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
     mOutput.finishFrame(std::move(result));
 }
@@ -3102,7 +3142,8 @@
     struct OutputPartialMock : public OutputPartialMockBase {
         // Sets up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
+        MOCK_METHOD(compositionengine::Output::FrameFences, presentFrame, ());
+        MOCK_METHOD(void, executeCommands, ());
     };
 
     struct Layer {
@@ -3140,9 +3181,67 @@
 };
 
 TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
     mOutput.mState.isEnabled = false;
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+    EXPECT_CALL(mOutput, presentFrame()).Times(0);
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifNotEnabledExecutesCommandsIfFlush) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = false;
+    EXPECT_CALL(mOutput, executeCommands());
+    EXPECT_CALL(mOutput, presentFrame()).Times(0);
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = true;
+
+    compositionengine::Output::FrameFences frameFences;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // This should only be called for disabled outputs. This test's goal is to verify this line;
+    // the other expectations help satisfy the StrictMocks.
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, ifEnabledDoNotExecuteCommands2) {
+    // Same test as ifEnabledDoNotExecuteCommands, but with this variable set to false.
+    constexpr bool kFlushEvenWhenDisabled = false;
+
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::flush_buffer_slots_to_uncache,
+                      true);
+    mOutput.mState.isEnabled = true;
+
+    compositionengine::Output::FrameFences frameFences;
+
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // This should only be called for disabled outputs. This test's goal is to verify this line;
+    // the other expectations help satisfy the StrictMocks.
+    EXPECT_CALL(mOutput, executeCommands()).Times(0);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) {
@@ -3160,10 +3259,13 @@
     EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
     EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = true;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
     // Simulate getting release fences from each layer, and ensure they are passed to the
     // front-end layer interface for each layer correctly.
 
@@ -3202,10 +3304,56 @@
                 EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get());
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, releaseFencesAreSetInLayerFE) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+    // Simulate getting release fences from each layer, and ensure they are passed to the
+    // front-end layer interface for each layer correctly.
+
+    mOutput.mState.isEnabled = true;
+
+    // Create three unique fence instances
+    sp<Fence> layer1Fence = sp<Fence>::make();
+    sp<Fence> layer2Fence = sp<Fence>::make();
+    sp<Fence> layer3Fence = sp<Fence>::make();
+
+    Output::FrameFences frameFences;
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, layer1Fence);
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, layer2Fence);
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, layer3Fence);
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Compare the pointers values of each fence to make sure the correct ones
+    // are passed. This happens to work with the current implementation, but
+    // would not survive certain calls like Fence::merge() which would return a
+    // new instance.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence(_))
+            .WillOnce([&layer1Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer1Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence(_))
+            .WillOnce([&layer2Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer2Fence), releaseFence);
+            });
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence(_))
+            .WillOnce([&layer3Fence](FenceResult releaseFence) {
+                EXPECT_EQ(FenceResult(layer3Fence), releaseFence);
+            });
+
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3225,10 +3373,40 @@
     EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return());
     EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return());
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+}
+
+TEST_F(OutputPostFramebufferTest, setReleaseFencesIncludeClientTargetAcquireFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    Output::FrameFences frameFences;
+    frameFences.clientTargetAcquireFence = sp<Fence>::make();
+    frameFences.layerFences.emplace(&mLayer1.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
+    frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Fence::merge is called, and since none of the fences are actually valid,
+    // Fence::NO_FENCE is returned and passed to each setReleaseFence() call.
+    // This is the best we can do without creating a real kernel fence object.
+    EXPECT_CALL(*mLayer1.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer2.layerFE, setReleaseFence).WillOnce(Return());
+    EXPECT_CALL(*mLayer3.layerFE, setReleaseFence).WillOnce(Return());
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 }
 
 TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, false);
+    ASSERT_FALSE(FlagManager::getInstance().ce_fence_promise());
+
     mOutput.mState.isEnabled = true;
     mOutput.mState.usesClientComposition = true;
 
@@ -3270,7 +3448,57 @@
                 EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
             });
 
-    mOutput.presentFrameAndReleaseLayers();
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
+
+    // After the call the list of released layers should have been cleared.
+    EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
+}
+
+TEST_F(OutputPostFramebufferTest, setReleasedLayersSentPresentFence) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::ce_fence_promise, true);
+    ASSERT_TRUE(FlagManager::getInstance().ce_fence_promise());
+
+    mOutput.mState.isEnabled = true;
+    mOutput.mState.usesClientComposition = true;
+
+    // This should happen even if there are no (current) output layers.
+    EXPECT_CALL(mOutput, getOutputLayerCount()).WillOnce(Return(0u));
+
+    // Load up the released layers with some mock instances
+    sp<StrictMock<mock::LayerFE>> releasedLayer1 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer2 = sp<StrictMock<mock::LayerFE>>::make();
+    sp<StrictMock<mock::LayerFE>> releasedLayer3 = sp<StrictMock<mock::LayerFE>>::make();
+    Output::ReleasedLayers layers;
+    layers.push_back(releasedLayer1);
+    layers.push_back(releasedLayer2);
+    layers.push_back(releasedLayer3);
+    mOutput.setReleasedLayers(std::move(layers));
+
+    // Set up a fake present fence
+    sp<Fence> presentFence = sp<Fence>::make();
+    Output::FrameFences frameFences;
+    frameFences.presentFence = presentFence;
+
+    EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
+    EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
+
+    // Each released layer should be given the presentFence.
+    EXPECT_CALL(*releasedLayer1, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer2, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+    EXPECT_CALL(*releasedLayer3, setReleaseFence(_))
+            .WillOnce([&presentFence](FenceResult fenceResult) {
+                EXPECT_EQ(FenceResult(presentFence), fenceResult);
+            });
+
+    constexpr bool kFlushEvenWhenDisabled = false;
+    mOutput.presentFrameAndReleaseLayers(kFlushEvenWhenDisabled);
 
     // After the call the list of released layers should have been cleared.
     EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
@@ -3293,9 +3521,12 @@
         MOCK_METHOD2(appendRegionFlashRequests,
                      void(const Region&, std::vector<LayerFE::LayerSettings>&));
         MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
+        MOCK_METHOD(void, setHintSessionGpuStart, (TimePoint startTime), (override));
         MOCK_METHOD(void, setHintSessionGpuFence, (std::unique_ptr<FenceTime> && gpuFence),
                     (override));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool), (override));
         MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
     };
 
     OutputComposeSurfacesTest() {
@@ -3325,6 +3556,8 @@
         EXPECT_CALL(mCompositionEngine, getTimeStats()).WillRepeatedly(Return(mTimeStats.get()));
         EXPECT_CALL(*mDisplayColorProfile, getHdrCapabilities())
                 .WillRepeatedly(ReturnRef(kHdrCapabilities));
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
     }
 
     struct ExecuteState : public CallOrderStateMachineHelper<TestType, ExecuteState> {
@@ -3590,10 +3823,57 @@
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
     // We do not expect another call to draw layers.
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
     verify().execute().expectAFenceWasReturned();
     EXPECT_TRUE(mOutput.mState.reusedClientComposition);
 }
 
+TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChangesWithAdpfGpuOff) {
+    EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillOnce(Return(false));
+    LayerFE::LayerSettings r1;
+    LayerFE::LayerSettings r2;
+
+    r1.geometry.boundaries = FloatRect{1, 2, 3, 4};
+    r2.geometry.boundaries = FloatRect{5, 6, 7, 8};
+
+    EXPECT_CALL(mOutput, getSkipColorTransform()).WillRepeatedly(Return(false));
+    EXPECT_CALL(*mDisplayColorProfile, hasWideColorGamut()).WillRepeatedly(Return(true));
+    EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
+    EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+            .WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{r1, r2}));
+    EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
+            .WillRepeatedly(Return());
+
+    const auto otherOutputBuffer = std::make_shared<
+            renderengine::impl::
+                    ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
+                                     renderengine::impl::ExternalTexture::Usage::READABLE |
+                                             renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+    EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
+            .WillOnce(Return(mOutputBuffer))
+            .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+            .WillRepeatedly([&](const renderengine::DisplaySettings&,
+                                const std::vector<renderengine::LayerSettings>&,
+                                const std::shared_ptr<renderengine::ExternalTexture>&,
+                                base::unique_fd&&) -> ftl::Future<FenceResult> {
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
+            });
+
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_)).Times(0);
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_)).Times(0);
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+
+    verify().execute().expectAFenceWasReturned();
+    EXPECT_FALSE(mOutput.mState.reusedClientComposition);
+}
+
 TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
     LayerFE::LayerSettings r1;
     LayerFE::LayerSettings r2;
@@ -3618,14 +3898,18 @@
     EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
             .WillOnce(Return(mOutputBuffer))
             .WillOnce(Return(otherOutputBuffer));
+    base::unique_fd fd(open("/dev/null", O_RDONLY));
     EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
             .WillRepeatedly([&](const renderengine::DisplaySettings&,
                                 const std::vector<renderengine::LayerSettings>&,
                                 const std::shared_ptr<renderengine::ExternalTexture>&,
                                 base::unique_fd&&) -> ftl::Future<FenceResult> {
-                return ftl::yield<FenceResult>(Fence::NO_FENCE);
+                return ftl::yield<FenceResult>(sp<Fence>::make(std::move(fd)));
             });
 
+    EXPECT_CALL(mOutput, setHintSessionRequiresRenderEngine(true));
+    EXPECT_CALL(mOutput, setHintSessionGpuStart(_));
+    EXPECT_CALL(mOutput, setHintSessionGpuFence(_));
     verify().execute().expectAFenceWasReturned();
     EXPECT_FALSE(mOutput.mState.reusedClientComposition);
 
@@ -5057,8 +5341,9 @@
     struct OutputPartialMock : public OutputPrepareFrameAsyncTest::OutputPartialMock {
         // Set up the helper functions called by the function under test to use
         // mock implementations.
-        MOCK_METHOD0(presentFrameAndReleaseLayers, void());
-        MOCK_METHOD0(presentFrameAndReleaseLayersAsync, ftl::Future<std::monostate>());
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled));
+        MOCK_METHOD(ftl::Future<std::monostate>, presentFrameAndReleaseLayersAsync,
+                    (bool flushEvenWhenDisabled));
     };
     OutputPresentFrameAndReleaseLayersAsyncTest() {
         mOutput->setDisplayColorProfileForTest(
@@ -5075,16 +5360,16 @@
 };
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, notCalledWhenNotRequested) {
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()).Times(0);
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(_)).Times(0);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(1);
 
     mOutput->present(mRefreshArgs);
 }
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) {
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(false))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(0);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(_)).Times(0);
 
     mOutput->offloadPresentNextFrame();
     mOutput->present(mRefreshArgs);
@@ -5092,9 +5377,10 @@
 
 TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledForOneFrame) {
     ::testing::InSequence inseq;
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync(kFlushEvenWhenDisabled))
             .WillOnce(Return(ftl::yield<std::monostate>({})));
-    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+    EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled)).Times(1);
 
     mOutput->offloadPresentNextFrame();
     mOutput->present(mRefreshArgs);
@@ -5175,5 +5461,59 @@
     mOutput.updateProtectedContentState();
 }
 
+struct OutputPresentFrameAndReleaseLayersTest : public testing::Test {
+    struct OutputPartialMock : public OutputPartialMockBase {
+        // Sets up the helper functions called by the function under test (and functions we can
+        // ignore) to use mock implementations.
+        MOCK_METHOD1(updateColorProfile, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(updateCompositionState,
+                     void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(planComposition, void());
+        MOCK_METHOD1(writeCompositionState, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD0(beginFrame, void());
+        MOCK_METHOD0(prepareFrame, void());
+        MOCK_METHOD0(prepareFrameAsync, GpuCompositionResult());
+        MOCK_METHOD1(devOptRepaintFlash, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
+        MOCK_METHOD(void, presentFrameAndReleaseLayers, (bool flushEvenWhenDisabled), (override));
+        MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
+        MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
+        MOCK_METHOD(void, setHintSessionRequiresRenderEngine, (bool requiresRenderEngine),
+                    (override));
+        MOCK_METHOD(bool, isPowerHintSessionEnabled, (), (override));
+        MOCK_METHOD(bool, isPowerHintSessionGpuReportingEnabled, (), (override));
+    };
+
+    OutputPresentFrameAndReleaseLayersTest() {
+        EXPECT_CALL(mOutput, isPowerHintSessionEnabled()).WillRepeatedly(Return(true));
+        EXPECT_CALL(mOutput, isPowerHintSessionGpuReportingEnabled()).WillRepeatedly(Return(true));
+    }
+
+    NiceMock<OutputPartialMock> mOutput;
+};
+
+TEST_F(OutputPresentFrameAndReleaseLayersTest, noBuffersToUncache) {
+    CompositionRefreshArgs args;
+    ASSERT_TRUE(args.bufferIdsToUncache.empty());
+    mOutput.editState().isEnabled = false;
+
+    constexpr bool kFlushEvenWhenDisabled = false;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
+
+    mOutput.present(args);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersTest, buffersToUncache) {
+    CompositionRefreshArgs args;
+    args.bufferIdsToUncache.push_back(1);
+    mOutput.editState().isEnabled = false;
+
+    constexpr bool kFlushEvenWhenDisabled = true;
+    EXPECT_CALL(mOutput, presentFrameAndReleaseLayers(kFlushEvenWhenDisabled));
+
+    mOutput.present(args);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index d2eff75..54ee0ef 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -1339,6 +1339,55 @@
     EXPECT_EQ(nullptr, overrideBuffer3);
 }
 
+TEST_F(FlattenerTest, flattenLayers_skipsLayersDisablingDimming) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+    // The third layer disables dimming, which means it should not be cached
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    mTestLayers[2]->layerFECompositionState.dimmingEnabled = false;
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    mTime += 200ms;
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+
+    // This will render a CachedSet.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+            .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+    // We've rendered a CachedSet, but we haven't merged it in.
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+
+    // This time we merge the CachedSet in, so we have a new hash, and we should
+    // only have two sets.
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
+
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
 TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
     auto& layerState1 = mTestLayers[0]->layerState;
     const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 044917e..03758b3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "LayerStateTest"
 
 #include <aidl/android/hardware/graphics/common/BufferUsage.h>
+#include <common/include/common/test/FlagUtils.h>
 #include <compositionengine/impl/OutputLayer.h>
 #include <compositionengine/impl/planner/LayerState.h>
 #include <compositionengine/mock/LayerFE.h>
@@ -26,6 +27,7 @@
 #include <log/log.h>
 
 #include "android/hardware_buffer.h"
+#include "com_android_graphics_surfaceflinger_flags.h"
 #include "compositionengine/LayerFECompositionState.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -304,6 +306,16 @@
     EXPECT_EQ(Composition::CLIENT, mLayerState->getCompositionType());
 }
 
+TEST_F(LayerStateTest, getHdrSdrRatio) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.currentHdrSdrRatio = 2.f;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_EQ(2.f, mLayerState->getHdrSdrRatio());
+}
+
 TEST_F(LayerStateTest, updateCompositionType) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
@@ -454,6 +466,9 @@
 }
 
 TEST_F(LayerStateTest, compareSourceCrop) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     OutputLayerCompositionState outputLayerCompositionState;
     outputLayerCompositionState.sourceCrop = sFloatRectOne;
     LayerFECompositionState layerFECompositionState;
@@ -1033,6 +1048,47 @@
     EXPECT_TRUE(otherLayerState->compare(*mLayerState));
 }
 
+TEST_F(LayerStateTest, updateDimmingEnabled) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.dimmingEnabled = true;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_TRUE(mLayerState->isDimmingEnabled());
+
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.dimmingEnabled = false;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(ftl::Flags<LayerStateField>(LayerStateField::DimmingEnabled), updates);
+    EXPECT_FALSE(mLayerState->isDimmingEnabled());
+}
+
+TEST_F(LayerStateTest, compareDimmingEnabled) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.dimmingEnabled = true;
+    setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    mock::OutputLayer newOutputLayer;
+    sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.dimmingEnabled = false;
+    setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+    verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::DimmingEnabled);
+
+    EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+    EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
 TEST_F(LayerStateTest, dumpDoesNotCrash) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 35d0ffb..a1210b4 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
@@ -18,6 +18,9 @@
 #undef LOG_TAG
 #define LOG_TAG "PredictorTest"
 
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
 #include <compositionengine/impl/planner/Predictor.h>
 #include <compositionengine/mock/LayerFE.h>
 #include <compositionengine/mock/OutputLayer.h>
@@ -127,6 +130,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInSingleLayer) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -158,6 +164,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_matchesSingleDifferenceInMultiLayerStack) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -304,6 +313,9 @@
 }
 
 TEST_F(LayerStackTest, getApproximateMatch_doesNotMatchMultipleApproximations) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -347,6 +359,9 @@
 };
 
 TEST_F(LayerStackTest, reorderingChangesNonBufferHash) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -467,6 +482,9 @@
 }
 
 TEST_F(PredictorTest, getPredictedPlan_recordCandidateAndRetrieveApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
@@ -504,6 +522,9 @@
 }
 
 TEST_F(PredictorTest, recordMissedPlan_skipsApproximateMatch) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+                              cache_when_source_crop_layer_only_moved,
+                      false);
     mock::OutputLayer outputLayerOne;
     sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::make();
     OutputLayerCompositionState outputLayerCompositionStateOne{
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
new file mode 100644
index 0000000..f8b6c6e
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright 2024 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 "DisplayModeController"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "Display/DisplayModeController.h"
+#include "Display/DisplaySnapshot.h"
+#include "DisplayHardware/HWComposer.h"
+
+#include <android-base/properties.h>
+#include <common/FlagManager.h>
+#include <common/trace.h>
+#include <ftl/concat.h>
+#include <ftl/expected.h>
+#include <log/log.h>
+
+namespace android::display {
+
+template <size_t N>
+inline std::string DisplayModeController::Display::concatId(const char (&str)[N]) const {
+    return std::string(ftl::Concat(str, ' ', snapshot.get().displayId().value).str());
+}
+
+DisplayModeController::Display::Display(DisplaySnapshotRef snapshot,
+                                        RefreshRateSelectorPtr selectorPtr)
+      : snapshot(snapshot),
+        selectorPtr(std::move(selectorPtr)),
+        pendingModeFpsTrace(concatId("PendingModeFps")),
+        activeModeFpsTrace(concatId("ActiveModeFps")),
+        renderRateFpsTrace(concatId("RenderRateFps")),
+        hasDesiredModeTrace(concatId("HasDesiredMode"), false) {}
+
+void DisplayModeController::registerDisplay(PhysicalDisplayId displayId,
+                                            DisplaySnapshotRef snapshotRef,
+                                            RefreshRateSelectorPtr selectorPtr) {
+    std::lock_guard lock(mDisplayLock);
+    mDisplays.emplace_or_replace(displayId, std::make_unique<Display>(snapshotRef, selectorPtr));
+}
+
+void DisplayModeController::registerDisplay(DisplaySnapshotRef snapshotRef,
+                                            DisplayModeId activeModeId,
+                                            scheduler::RefreshRateSelector::Config config) {
+    const auto& snapshot = snapshotRef.get();
+    const auto displayId = snapshot.displayId();
+
+    std::lock_guard lock(mDisplayLock);
+    mDisplays.emplace_or_replace(displayId,
+                                 std::make_unique<Display>(snapshotRef, snapshot.displayModes(),
+                                                           activeModeId, config));
+}
+
+void DisplayModeController::unregisterDisplay(PhysicalDisplayId displayId) {
+    std::lock_guard lock(mDisplayLock);
+    const bool ok = mDisplays.erase(displayId);
+    ALOGE_IF(!ok, "%s: Unknown display %s", __func__, to_string(displayId).c_str());
+}
+
+auto DisplayModeController::selectorPtrFor(PhysicalDisplayId displayId) const
+        -> RefreshRateSelectorPtr {
+    std::lock_guard lock(mDisplayLock);
+    return mDisplays.get(displayId)
+            .transform([](const DisplayPtr& displayPtr) { return displayPtr->selectorPtr; })
+            .value_or(nullptr);
+}
+
+auto DisplayModeController::setDesiredMode(PhysicalDisplayId displayId,
+                                           DisplayModeRequest&& desiredMode) -> DesiredModeAction {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DesiredModeAction::None)).get();
+
+    {
+        SFTRACE_NAME(displayPtr->concatId(__func__).c_str());
+        ALOGD("%s %s", displayPtr->concatId(__func__).c_str(), to_string(desiredMode).c_str());
+
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+
+        if (auto& desiredModeOpt = displayPtr->desiredModeOpt) {
+            // A mode transition was already scheduled, so just override the desired mode.
+            const bool emitEvent = desiredModeOpt->emitEvent;
+            const bool force = desiredModeOpt->force;
+            desiredModeOpt = std::move(desiredMode);
+            desiredModeOpt->emitEvent |= emitEvent;
+            if (FlagManager::getInstance().connected_display()) {
+                desiredModeOpt->force |= force;
+            }
+            return DesiredModeAction::None;
+        }
+
+        // If the desired mode is already active...
+        const auto activeMode = displayPtr->selectorPtr->getActiveMode();
+        if (const auto& desiredModePtr = desiredMode.mode.modePtr;
+            !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+            if (activeMode == desiredMode.mode) {
+                return DesiredModeAction::None;
+            }
+
+            // ...but the render rate changed:
+            setActiveModeLocked(displayId, desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
+                                desiredMode.mode.fps);
+            return DesiredModeAction::InitiateRenderRateSwitch;
+        }
+
+        // Restore peak render rate to schedule the next frame as soon as possible.
+        setActiveModeLocked(displayId, activeMode.modePtr->getId(),
+                            activeMode.modePtr->getVsyncRate(), activeMode.modePtr->getPeakFps());
+
+        // Initiate a mode change.
+        displayPtr->desiredModeOpt = std::move(desiredMode);
+        displayPtr->hasDesiredModeTrace = true;
+    }
+
+    return DesiredModeAction::InitiateDisplayModeSwitch;
+}
+
+auto DisplayModeController::getDesiredMode(PhysicalDisplayId displayId) const
+        -> DisplayModeRequestOpt {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->desiredModeOpt;
+    }
+}
+
+auto DisplayModeController::getPendingMode(PhysicalDisplayId displayId) const
+        -> DisplayModeRequestOpt {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(DisplayModeRequestOpt())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->pendingModeOpt;
+    }
+}
+
+bool DisplayModeController::isModeSetPending(PhysicalDisplayId displayId) const {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        return displayPtr->isModeSetPending;
+    }
+}
+
+scheduler::FrameRateMode DisplayModeController::getActiveMode(PhysicalDisplayId displayId) const {
+    return selectorPtrFor(displayId)->getActiveMode();
+}
+
+void DisplayModeController::clearDesiredMode(PhysicalDisplayId displayId) {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+
+    {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        displayPtr->desiredModeOpt.reset();
+        displayPtr->hasDesiredModeTrace = false;
+    }
+}
+
+bool DisplayModeController::initiateModeChange(PhysicalDisplayId displayId,
+                                               DisplayModeRequest&& desiredMode,
+                                               const hal::VsyncPeriodChangeConstraints& constraints,
+                                               hal::VsyncPeriodChangeTimeline& outTimeline) {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_EXPECT(mDisplays.get(displayId).ok_or(false)).get();
+
+    // TODO: b/255635711 - Flow the DisplayModeRequest through the desired/pending/active states.
+    // For now, `desiredMode` and `desiredModeOpt` are one and the same, but the latter is not
+    // cleared until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been
+    // consumed at this point, so clear the `force` flag to prevent an endless loop of
+    // `initiateModeChange`.
+    if (FlagManager::getInstance().connected_display()) {
+        std::scoped_lock lock(displayPtr->desiredModeLock);
+        if (displayPtr->desiredModeOpt) {
+            displayPtr->desiredModeOpt->force = false;
+        }
+    }
+
+    displayPtr->pendingModeOpt = std::move(desiredMode);
+    displayPtr->isModeSetPending = true;
+
+    const auto& mode = *displayPtr->pendingModeOpt->mode.modePtr;
+
+    if (mComposerPtr->setActiveModeWithConstraints(displayId, mode.getHwcId(), constraints,
+                                                   &outTimeline) != OK) {
+        return false;
+    }
+
+    SFTRACE_INT(displayPtr->pendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+    return true;
+}
+
+void DisplayModeController::finalizeModeChange(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                               Fps vsyncRate, Fps renderFps) {
+    std::lock_guard lock(mDisplayLock);
+    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);
+
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+    displayPtr->isModeSetPending = false;
+}
+
+void DisplayModeController::setActiveMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                          Fps vsyncRate, Fps renderFps) {
+    std::lock_guard lock(mDisplayLock);
+    setActiveModeLocked(displayId, modeId, vsyncRate, renderFps);
+}
+
+void DisplayModeController::setActiveModeLocked(PhysicalDisplayId displayId, DisplayModeId modeId,
+                                                Fps vsyncRate, Fps renderFps) {
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+
+    SFTRACE_INT(displayPtr->activeModeFpsTrace.c_str(), vsyncRate.getIntValue());
+    SFTRACE_INT(displayPtr->renderRateFpsTrace.c_str(), renderFps.getIntValue());
+
+    displayPtr->selectorPtr->setActiveMode(modeId, renderFps);
+
+    if (mActiveModeListener) {
+        mActiveModeListener(displayId, vsyncRate, renderFps);
+    }
+}
+
+void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId) {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr = FTL_TRY(mDisplays.get(displayId).ok_or(ftl::Unit())).get();
+
+    const auto controllerOpt = displayPtr->selectorPtr->kernelIdleTimerController();
+    if (!controllerOpt) return;
+
+    using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction;
+
+    switch (displayPtr->selectorPtr->getIdleTimerAction()) {
+        case KernelIdleTimerAction::TurnOff:
+            if (displayPtr->isKernelIdleTimerEnabled) {
+                SFTRACE_INT("KernelIdleTimer", 0);
+                updateKernelIdleTimer(displayId, std::chrono::milliseconds::zero(), *controllerOpt);
+                displayPtr->isKernelIdleTimerEnabled = false;
+            }
+            break;
+        case KernelIdleTimerAction::TurnOn:
+            if (!displayPtr->isKernelIdleTimerEnabled) {
+                SFTRACE_INT("KernelIdleTimer", 1);
+                const auto timeout = displayPtr->selectorPtr->getIdleTimerTimeout();
+                updateKernelIdleTimer(displayId, timeout, *controllerOpt);
+                displayPtr->isKernelIdleTimerEnabled = true;
+            }
+            break;
+    }
+}
+
+void DisplayModeController::updateKernelIdleTimer(PhysicalDisplayId displayId,
+                                                  std::chrono::milliseconds timeout,
+                                                  KernelIdleTimerController controller) {
+    switch (controller) {
+        case KernelIdleTimerController::HwcApi:
+            mComposerPtr->setIdleTimerEnabled(displayId, timeout);
+            break;
+
+        case KernelIdleTimerController::Sysprop:
+            using namespace std::string_literals;
+            base::SetProperty("graphics.display.kernel_idle_timer.enabled"s,
+                              timeout > std::chrono::milliseconds::zero() ? "true"s : "false"s);
+            break;
+    }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
+auto DisplayModeController::getKernelIdleTimerState(PhysicalDisplayId displayId) const
+        -> KernelIdleTimerState {
+    std::lock_guard lock(mDisplayLock);
+    const auto& displayPtr =
+            FTL_EXPECT(mDisplays.get(displayId).ok_or(KernelIdleTimerState())).get();
+
+    const auto desiredModeIdOpt =
+            (std::scoped_lock(displayPtr->desiredModeLock), displayPtr->desiredModeOpt)
+                    .transform([](const display::DisplayModeRequest& request) {
+                        return request.mode.modePtr->getId();
+                    });
+
+    return {desiredModeIdOpt, displayPtr->isKernelIdleTimerEnabled};
+}
+
+#pragma clang diagnostic pop
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplayModeController.h b/services/surfaceflinger/Display/DisplayModeController.h
new file mode 100644
index 0000000..9ec603d
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplayModeController.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+#include <string>
+#include <utility>
+
+#include <android-base/thread_annotations.h>
+#include <ftl/function.h>
+#include <ftl/optional.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
+#include "Display/DisplayModeRequest.h"
+#include "Display/DisplaySnapshotRef.h"
+#include "DisplayHardware/DisplayMode.h"
+#include "Scheduler/RefreshRateSelector.h"
+#include "ThreadContext.h"
+#include "TracedOrdinal.h"
+
+namespace android {
+class HWComposer;
+} // namespace android
+
+namespace android::display {
+
+// Selects the DisplayMode of each physical display, in accordance with DisplayManager policy and
+// certain heuristic signals.
+class DisplayModeController {
+public:
+    using ActiveModeListener = ftl::Function<void(PhysicalDisplayId, Fps vsyncRate, Fps renderFps)>;
+
+    DisplayModeController() = default;
+
+    void setHwComposer(HWComposer* composerPtr) { mComposerPtr = composerPtr; }
+    void setActiveModeListener(const ActiveModeListener& listener) {
+        mActiveModeListener = listener;
+    }
+
+    // TODO: b/241285876 - Remove once ownership is no longer shared with DisplayDevice.
+    using RefreshRateSelectorPtr = std::shared_ptr<scheduler::RefreshRateSelector>;
+
+    // Used by tests to inject an existing RefreshRateSelector.
+    // TODO: b/241285876 - Remove this.
+    void registerDisplay(PhysicalDisplayId, DisplaySnapshotRef, RefreshRateSelectorPtr)
+            EXCLUDES(mDisplayLock);
+
+    // The referenced DisplaySnapshot must outlive the registration.
+    void registerDisplay(DisplaySnapshotRef, DisplayModeId, scheduler::RefreshRateSelector::Config)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    // Returns `nullptr` if the display is no longer registered (or never was).
+    RefreshRateSelectorPtr selectorPtrFor(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+
+    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
+
+    DesiredModeAction setDesiredMode(PhysicalDisplayId, DisplayModeRequest&&)
+            EXCLUDES(mDisplayLock);
+
+    using DisplayModeRequestOpt = ftl::Optional<DisplayModeRequest>;
+
+    DisplayModeRequestOpt getDesiredMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+    void clearDesiredMode(PhysicalDisplayId) EXCLUDES(mDisplayLock);
+
+    DisplayModeRequestOpt getPendingMode(PhysicalDisplayId) const REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+    bool isModeSetPending(PhysicalDisplayId) const REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+
+    scheduler::FrameRateMode getActiveMode(PhysicalDisplayId) const EXCLUDES(mDisplayLock);
+
+    bool initiateModeChange(PhysicalDisplayId, DisplayModeRequest&&,
+                            const hal::VsyncPeriodChangeConstraints&,
+                            hal::VsyncPeriodChangeTimeline& outTimeline)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    void finalizeModeChange(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+    void setActiveMode(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            EXCLUDES(mDisplayLock);
+
+    void updateKernelIdleTimer(PhysicalDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
+
+    struct KernelIdleTimerState {
+        std::optional<DisplayModeId> desiredModeIdOpt = std::nullopt;
+        bool isEnabled = false;
+    };
+
+    KernelIdleTimerState getKernelIdleTimerState(PhysicalDisplayId) const
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+
+private:
+    struct Display {
+        template <size_t N>
+        std::string concatId(const char (&)[N]) const;
+
+        Display(DisplaySnapshotRef, RefreshRateSelectorPtr);
+        Display(DisplaySnapshotRef snapshot, DisplayModes modes, DisplayModeId activeModeId,
+                scheduler::RefreshRateSelector::Config config)
+              : Display(snapshot,
+                        std::make_shared<scheduler::RefreshRateSelector>(std::move(modes),
+                                                                         activeModeId, config)) {}
+        const DisplaySnapshotRef snapshot;
+        const RefreshRateSelectorPtr selectorPtr;
+
+        const std::string pendingModeFpsTrace;
+        const std::string activeModeFpsTrace;
+        const std::string renderRateFpsTrace;
+
+        std::mutex desiredModeLock;
+        DisplayModeRequestOpt desiredModeOpt GUARDED_BY(desiredModeLock);
+        TracedOrdinal<bool> hasDesiredModeTrace GUARDED_BY(desiredModeLock);
+
+        DisplayModeRequestOpt pendingModeOpt GUARDED_BY(kMainThreadContext);
+        bool isModeSetPending GUARDED_BY(kMainThreadContext) = false;
+
+        bool isKernelIdleTimerEnabled GUARDED_BY(kMainThreadContext) = false;
+    };
+
+    using DisplayPtr = std::unique_ptr<Display>;
+
+    void setActiveModeLocked(PhysicalDisplayId, DisplayModeId, Fps vsyncRate, Fps renderFps)
+            REQUIRES(mDisplayLock);
+
+    using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
+    void updateKernelIdleTimer(PhysicalDisplayId, std::chrono::milliseconds timeout,
+                               KernelIdleTimerController) REQUIRES(mDisplayLock);
+
+    // Set once when initializing the DisplayModeController, which the HWComposer must outlive.
+    HWComposer* mComposerPtr = nullptr;
+
+    ActiveModeListener mActiveModeListener;
+
+    mutable std::mutex mDisplayLock;
+    ui::PhysicalDisplayMap<PhysicalDisplayId, DisplayPtr> mDisplays GUARDED_BY(mDisplayLock);
+};
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/DisplaySnapshotRef.h b/services/surfaceflinger/Display/DisplaySnapshotRef.h
new file mode 100644
index 0000000..6cc5f7e
--- /dev/null
+++ b/services/surfaceflinger/Display/DisplaySnapshotRef.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <functional>
+
+namespace android::display {
+
+class DisplaySnapshot;
+
+using DisplaySnapshotRef = std::reference_wrapper<const DisplaySnapshot>;
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h
index ef36234..9b1f1ed 100644
--- a/services/surfaceflinger/Display/PhysicalDisplay.h
+++ b/services/surfaceflinger/Display/PhysicalDisplay.h
@@ -25,6 +25,7 @@
 #include <utils/StrongPointer.h>
 
 #include "DisplaySnapshot.h"
+#include "DisplaySnapshotRef.h"
 
 namespace android::display {
 
@@ -45,8 +46,7 @@
 
     // Transformers for PhysicalDisplays::get.
 
-    using SnapshotRef = std::reference_wrapper<const DisplaySnapshot>;
-    SnapshotRef snapshotRef() const { return std::cref(mSnapshot); }
+    DisplaySnapshotRef snapshotRef() const { return std::cref(mSnapshot); }
 
     bool isInternal() const {
         return mSnapshot.connectionType() == ui::DisplayConnectionType::Internal;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 62f8fb1..402a3d2 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -24,7 +24,7 @@
 
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/DisplayColorProfile.h>
@@ -36,6 +36,7 @@
 #include <compositionengine/RenderSurfaceCreationArgs.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <configstore/Utils.h>
+#include <ftl/concat.h>
 #include <log/log.h>
 #include <system/window.h>
 
@@ -64,15 +65,11 @@
         mDisplayToken(args.displayToken),
         mSequenceId(args.sequenceId),
         mCompositionDisplay{args.compositionDisplay},
-        mPendingModeFpsTrace(concatId("PendingModeFps")),
-        mActiveModeFpsTrace(concatId("ActiveModeFps")),
-        mRenderRateFpsTrace(concatId("RenderRateFps")),
         mPhysicalOrientation(args.physicalOrientation),
         mPowerMode(ftl::Concat("PowerMode ", getId().value).c_str(), args.initialPowerMode),
         mIsPrimary(args.isPrimary),
         mRequestedRefreshRate(args.requestedRefreshRate),
-        mRefreshRateSelector(std::move(args.refreshRateSelector)),
-        mHasDesiredModeTrace(concatId("HasDesiredMode"), false) {
+        mRefreshRateSelector(std::move(args.refreshRateSelector)) {
     mCompositionDisplay->editState().isSecure = args.isSecure;
     mCompositionDisplay->editState().isProtected = args.isProtected;
     mCompositionDisplay->createRenderSurface(
@@ -137,7 +134,7 @@
 
 auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
     gui::DisplayInfo info;
-    info.displayId = getLayerStack().id;
+    info.displayId = ui::LogicalDisplayId{static_cast<int32_t>(getLayerStack().id)};
 
     // The physical orientation is set when the orientation of the display panel is
     // different than the default orientation of the device. Other services like
@@ -204,60 +201,6 @@
     return mPowerMode != hal::PowerMode::OFF;
 }
 
-void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
-    ATRACE_INT(mActiveModeFpsTrace.c_str(), vsyncRate.getIntValue());
-    ATRACE_INT(mRenderRateFpsTrace.c_str(), renderFps.getIntValue());
-
-    mRefreshRateSelector->setActiveMode(modeId, renderFps);
-    updateRefreshRateOverlayRate(vsyncRate, renderFps);
-}
-
-bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
-                                       const hal::VsyncPeriodChangeConstraints& constraints,
-                                       hal::VsyncPeriodChangeTimeline& outTimeline) {
-    // TODO(b/255635711): Flow the DisplayModeRequest through the desired/pending/active states. For
-    // now, `desiredMode` and `mDesiredModeOpt` are one and the same, but the latter is not cleared
-    // until the next `SF::initiateDisplayModeChanges`. However, the desired mode has been consumed
-    // at this point, so clear the `force` flag to prevent an endless loop of `initiateModeChange`.
-    if (FlagManager::getInstance().connected_display()) {
-        std::scoped_lock lock(mDesiredModeLock);
-        if (mDesiredModeOpt) {
-            mDesiredModeOpt->force = false;
-        }
-    }
-
-    mPendingModeOpt = std::move(desiredMode);
-    mIsModeSetPending = true;
-
-    const auto& mode = *mPendingModeOpt->mode.modePtr;
-
-    if (mHwComposer.setActiveModeWithConstraints(getPhysicalId(), mode.getHwcId(), constraints,
-                                                 &outTimeline) != OK) {
-        return false;
-    }
-
-    ATRACE_INT(mPendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
-    return true;
-}
-
-void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
-    setActiveMode(modeId, vsyncRate, renderFps);
-    mIsModeSetPending = false;
-}
-
-nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const {
-    const auto physicalId = getPhysicalId();
-    if (!mHwComposer.isConnected(physicalId)) {
-        return 0;
-    }
-
-    if (const auto vsyncPeriodOpt = mHwComposer.getDisplayVsyncPeriod(physicalId).value_opt()) {
-        return *vsyncPeriodOpt;
-    }
-
-    return refreshRateSelector().getActiveMode().modePtr->getVsyncRate().getPeriodNsecs();
-}
-
 ui::Dataspace DisplayDevice::getCompositionDataSpace() const {
     return mCompositionDisplay->getState().dataspace;
 }
@@ -443,15 +386,16 @@
 }
 
 void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mHdrSdrRatio = currentHdrSdrRatio;
     if (mHdrSdrRatioOverlay) {
         mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
     }
 }
 
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner,
-                                             bool showRenderRate, bool showInMiddle) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate,
+                                             Fps renderFps, bool showSpinner, bool showRenderRate,
+                                             bool showInMiddle) {
     if (!enable) {
         mRefreshRateOverlay.reset();
         return;
@@ -474,22 +418,23 @@
         features |= RefreshRateOverlay::Features::SetByHwc;
     }
 
-    // TODO(b/296636258) Update to use the render rate range in VRR mode.
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
     mRefreshRateOverlay = RefreshRateOverlay::create(fpsRange, features);
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->setLayerStack(getLayerStack());
         mRefreshRateOverlay->setViewport(getSize());
-        updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps,
-                                     setByHwc);
+        updateRefreshRateOverlayRate(refreshRate, renderFps, setByHwc);
     }
 }
 
-void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) {
-    ATRACE_CALL();
+void DisplayDevice::updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc) {
+    SFTRACE_CALL();
     if (mRefreshRateOverlay) {
         if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
-            mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps);
+            if (mRefreshRateSelector->isVrrDevice() && !mRefreshRateOverlay->isSetByHwc()) {
+                refreshRate = renderFps;
+            }
+            mRefreshRateOverlay->changeRefreshRate(refreshRate, renderFps);
         } else {
             mRefreshRateOverlay->changeRenderRate(renderFps);
         }
@@ -510,6 +455,12 @@
     return false;
 }
 
+void DisplayDevice::onVrrIdle(bool idle) {
+    if (mRefreshRateOverlay) {
+        mRefreshRateOverlay->onVrrIdle(idle);
+    }
+}
+
 void DisplayDevice::animateOverlay() {
     if (mRefreshRateOverlay) {
         mRefreshRateOverlay->animate();
@@ -529,59 +480,6 @@
     }
 }
 
-auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode) -> DesiredModeAction {
-    ATRACE_NAME(concatId(__func__).c_str());
-    ALOGD("%s %s", concatId(__func__).c_str(), to_string(desiredMode).c_str());
-
-    std::scoped_lock lock(mDesiredModeLock);
-    if (mDesiredModeOpt) {
-        // A mode transition was already scheduled, so just override the desired mode.
-        const bool emitEvent = mDesiredModeOpt->emitEvent;
-        const bool force = mDesiredModeOpt->force;
-        mDesiredModeOpt = std::move(desiredMode);
-        mDesiredModeOpt->emitEvent |= emitEvent;
-        if (FlagManager::getInstance().connected_display()) {
-            mDesiredModeOpt->force |= force;
-        }
-        return DesiredModeAction::None;
-    }
-
-    // If the desired mode is already active...
-    const auto activeMode = refreshRateSelector().getActiveMode();
-    if (const auto& desiredModePtr = desiredMode.mode.modePtr;
-        !desiredMode.force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
-        if (activeMode == desiredMode.mode) {
-            return DesiredModeAction::None;
-        }
-
-        // ...but the render rate changed:
-        setActiveMode(desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
-                      desiredMode.mode.fps);
-        return DesiredModeAction::InitiateRenderRateSwitch;
-    }
-
-    // Set the render frame rate to the active physical refresh rate to schedule the next
-    // frame as soon as possible.
-    setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
-                  activeMode.modePtr->getVsyncRate());
-
-    // Initiate a mode change.
-    mDesiredModeOpt = std::move(desiredMode);
-    mHasDesiredModeTrace = true;
-    return DesiredModeAction::InitiateDisplayModeSwitch;
-}
-
-auto DisplayDevice::getDesiredMode() const -> DisplayModeRequestOpt {
-    std::scoped_lock lock(mDesiredModeLock);
-    return mDesiredModeOpt;
-}
-
-void DisplayDevice::clearDesiredMode() {
-    std::scoped_lock lock(mDesiredModeLock);
-    mDesiredModeOpt.reset();
-    mHasDesiredModeTrace = false;
-}
-
 void DisplayDevice::adjustRefreshRate(Fps pacesetterDisplayRefreshRate) {
     using fps_approx_ops::operator<=;
     if (mRequestedRefreshRate <= 0_Hz) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index edd57cc..3e3f558 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -23,8 +23,6 @@
 #include <android-base/thread_annotations.h>
 #include <android/native_window.h>
 #include <binder/IBinder.h>
-#include <ftl/concat.h>
-#include <ftl/optional.h>
 #include <gui/LayerState.h>
 #include <math/mat4.h>
 #include <renderengine/RenderEngine.h>
@@ -42,7 +40,6 @@
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
 
-#include "Display/DisplayModeRequest.h"
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
@@ -89,7 +86,7 @@
         return mCompositionDisplay;
     }
 
-    bool isVirtual() const { return VirtualDisplayId::tryCast(getId()).has_value(); }
+    bool isVirtual() const { return getId().isVirtual(); }
     bool isPrimary() const { return mIsPrimary; }
 
     // isSecure indicates whether this display can be trusted to display
@@ -183,37 +180,6 @@
 
     ui::Dataspace getCompositionDataSpace() const;
 
-    /* ------------------------------------------------------------------------
-     * Display mode management.
-     */
-
-    enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
-
-    DesiredModeAction setDesiredMode(display::DisplayModeRequest&&) EXCLUDES(mDesiredModeLock);
-
-    using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
-
-    DisplayModeRequestOpt getDesiredMode() const EXCLUDES(mDesiredModeLock);
-    void clearDesiredMode() EXCLUDES(mDesiredModeLock);
-
-    DisplayModeRequestOpt getPendingMode() const REQUIRES(kMainThreadContext) {
-        return mPendingModeOpt;
-    }
-    bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; }
-
-    scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
-        return mRefreshRateSelector->getActiveMode();
-    }
-
-    void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
-
-    bool initiateModeChange(display::DisplayModeRequest&&, const hal::VsyncPeriodChangeConstraints&,
-                            hal::VsyncPeriodChangeTimeline& outTimeline)
-            REQUIRES(kMainThreadContext);
-
-    void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
-            REQUIRES(kMainThreadContext);
-
     scheduler::RefreshRateSelector& refreshRateSelector() const { return *mRefreshRateSelector; }
 
     // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice.
@@ -221,22 +187,22 @@
         return mRefreshRateSelector;
     }
 
-    void animateOverlay();
-
     // Enables an overlay to be displayed with the current refresh rate
-    void enableRefreshRateOverlay(bool enable, bool setByHwc, bool showSpinner, bool showRenderRate,
-                                  bool showInMiddle) REQUIRES(kMainThreadContext);
-    void updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc = false);
+    // TODO(b/241285876): Move overlay to DisplayModeController.
+    void enableRefreshRateOverlay(bool enable, bool setByHwc, Fps refreshRate, Fps renderFps,
+                                  bool showSpinner, bool showRenderRate, bool showInMiddle)
+            REQUIRES(kMainThreadContext);
+    void updateRefreshRateOverlayRate(Fps refreshRate, Fps renderFps, bool setByHwc = false);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
+    void animateOverlay();
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
+    void onVrrIdle(bool idle);
 
     // Enables an overlay to be display with the hdr/sdr ratio
     void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext);
     void updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio);
     bool isHdrSdrRatioOverlayEnabled() const { return mHdrSdrRatioOverlay != nullptr; }
 
-    nsecs_t getVsyncPeriodFromHWC() const;
-
     Fps getAdjustedRefreshRate() const { return mAdjustedRefreshRate; }
 
     // Round the requested refresh rate to match a divisor of the pacesetter
@@ -249,11 +215,6 @@
     void dump(utils::Dumper&) const;
 
 private:
-    template <size_t N>
-    inline std::string concatId(const char (&str)[N]) const {
-        return std::string(ftl::Concat(str, ' ', getId().value).str());
-    }
-
     const sp<SurfaceFlinger> mFlinger;
     HWComposer& mHwComposer;
     const wp<IBinder> mDisplayToken;
@@ -262,9 +223,6 @@
     const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
 
     std::string mDisplayName;
-    std::string mPendingModeFpsTrace;
-    std::string mActiveModeFpsTrace;
-    std::string mRenderRateFpsTrace;
 
     const ui::Rotation mPhysicalOrientation;
     ui::Rotation mOrientation = ui::ROTATION_0;
@@ -296,13 +254,6 @@
     std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
     // This parameter is only used for hdr/sdr ratio overlay
     float mHdrSdrRatio = 1.0f;
-
-    mutable std::mutex mDesiredModeLock;
-    DisplayModeRequestOpt mDesiredModeOpt GUARDED_BY(mDesiredModeLock);
-    TracedOrdinal<bool> mHasDesiredModeTrace GUARDED_BY(mDesiredModeLock);
-
-    DisplayModeRequestOpt mPendingModeOpt GUARDED_BY(kMainThreadContext);
-    bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
 };
 
 struct DisplayDeviceState {
@@ -329,6 +280,7 @@
     uint32_t width = 0;
     uint32_t height = 0;
     std::string displayName;
+    std::string uniqueId;
     bool isSecure = false;
     bool isProtected = false;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
@@ -363,7 +315,6 @@
     hardware::graphics::composer::hal::PowerMode initialPowerMode{
             hardware::graphics::composer::hal::PowerMode::OFF};
     bool isPrimary{false};
-    DisplayModeId activeModeId;
     // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
     Fps requestedRefreshRate;
 };
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 362ab9c..66237b9 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -25,9 +25,8 @@
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
 #include <common/FlagManager.h>
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <aidl/android/hardware/graphics/composer3/BnComposerCallback.h>
 
@@ -45,6 +44,7 @@
 using aidl::android::hardware::graphics::composer3::BnComposerCallback;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
+using aidl::android::hardware::graphics::composer3::Lut;
 using aidl::android::hardware::graphics::composer3::PowerMode;
 using aidl::android::hardware::graphics::composer3::VirtualDisplay;
 
@@ -60,6 +60,7 @@
 using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities;
 using AidlHdrConversionCapability =
         aidl::android::hardware::graphics::common::HdrConversionCapability;
+using AidlHdcpLevels = aidl::android::hardware::drm::HdcpLevels;
 using AidlHdrConversionStrategy = aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties;
 using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata;
@@ -223,6 +224,12 @@
         return ::ndk::ScopedAStatus::ok();
     }
 
+    ::ndk::ScopedAStatus onHdcpLevelsChanged(int64_t in_display,
+                                             const AidlHdcpLevels& levels) override {
+        mCallback.onComposerHalHdcpLevelsChanged(translate<Display>(in_display), levels);
+        return ::ndk::ScopedAStatus::ok();
+    }
+
 private:
     HWC2::ComposerCallback& mCallback;
 };
@@ -677,7 +684,7 @@
 
 Error AidlComposer::presentDisplay(Display display, int* outPresentFence) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcPresentDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
@@ -810,7 +817,7 @@
                                     int32_t frameIntervalNs, uint32_t* outNumTypes,
                                     uint32_t* outNumRequests) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcValidateDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
@@ -840,7 +847,7 @@
                                              uint32_t* outNumRequests, int* outPresentFence,
                                              uint32_t* state) {
     const auto displayId = translate<int64_t>(display);
-    ATRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
+    SFTRACE_FORMAT("HwcPresentOrValidateDisplay %" PRId64, displayId);
 
     Error error = Error::NONE;
     mMutex.lock_shared();
@@ -1540,6 +1547,30 @@
     return error;
 }
 
+Error AidlComposer::getRequestedLuts(Display display, std::vector<DisplayLuts::LayerLut>* outLuts) {
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto reader = getReader(display)) {
+        *outLuts = reader->get().takeDisplayLuts(translate<int64_t>(display));
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
+}
+
+Error AidlComposer::setLayerLuts(Display display, Layer layer, std::vector<Lut>& luts) {
+    Error error = Error::NONE;
+    mMutex.lock_shared();
+    if (auto writer = getWriter(display)) {
+        writer->get().setLayerLuts(translate<int64_t>(display), translate<int64_t>(layer), luts);
+    } else {
+        error = Error::BAD_DISPLAY;
+    }
+    mMutex.unlock_shared();
+    return error;
+}
+
 Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) {
     Error error = Error::NONE;
     mMutex.lock_shared();
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index ea0e53a..246223a 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -244,6 +244,13 @@
     Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
     Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
                                 int32_t frameIntervalNs) override;
+    Error getRequestedLuts(
+            Display display,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
+                    outLuts) override;
+    Error setLayerLuts(
+            Display display, Layer layer,
+            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index bc067a0..7db9a94 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -40,7 +40,9 @@
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
+#include <aidl/android/hardware/graphics/composer3/Lut.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 #include <aidl/android/hardware/graphics/common/Transform.h>
@@ -303,6 +305,9 @@
     virtual Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) = 0;
     virtual Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
                                         int32_t frameIntervalNs) = 0;
+    virtual Error getRequestedLuts(Display display,
+                                   std::vector<V3_0::DisplayLuts::LayerLut>* outLuts) = 0;
+    virtual Error setLayerLuts(Display display, Layer layer, std::vector<V3_0::Lut>& luts) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index c77cdd4..748765a 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
@@ -31,10 +31,11 @@
 #include <utils/String8.h>
 #include <log/log.h>
 
-#include <hardware/hardware.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/BufferItem.h>
 #include <gui/BufferQueue.h>
 #include <gui/Surface.h>
+#include <hardware/hardware.h>
 
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
@@ -48,10 +49,18 @@
 
 using ui::Dataspace;
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
+                                       const sp<IGraphicBufferProducer>& producer,
+                                       const sp<IGraphicBufferConsumer>& consumer,
+                                       const ui::Size& size, const ui::Size& maxSize)
+      : ConsumerBase(producer, consumer),
+#else
 FramebufferSurface::FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
                                        const sp<IGraphicBufferConsumer>& consumer,
                                        const ui::Size& size, const ui::Size& maxSize)
       : ConsumerBase(consumer),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         mDisplayId(displayId),
         mMaxSize(maxSize),
         mCurrentBufferSlot(-1),
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index 2728cf6..6ca64a2 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 #include <sys/types.h>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <compositionengine/DisplaySurface.h>
 #include <gui/BufferQueue.h>
 #include <gui/ConsumerBase.h>
@@ -40,9 +41,16 @@
 
 class FramebufferSurface : public ConsumerBase, public compositionengine::DisplaySurface {
 public:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+    FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
+                       const sp<IGraphicBufferProducer>& producer,
+                       const sp<IGraphicBufferConsumer>& consumer, const ui::Size& size,
+                       const ui::Size& maxSize);
+#else
     FramebufferSurface(HWComposer& hwc, PhysicalDisplayId displayId,
                        const sp<IGraphicBufferConsumer>& consumer, const ui::Size& size,
                        const ui::Size& maxSize);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
     virtual status_t beginFrame(bool mustRecompose);
     virtual status_t prepareFrame(CompositionType compositionType);
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 8c0f81e..f1fa938 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -41,6 +41,8 @@
 using aidl::android::hardware::graphics::composer3::Composition;
 using AidlCapability = aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::DisplayLuts;
+using aidl::android::hardware::graphics::composer3::Lut;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
@@ -607,6 +609,19 @@
     return static_cast<Error>(error);
 }
 
+Error Display::getRequestedLuts(std::vector<DisplayLuts::LayerLut>* outLayerLuts) {
+    std::vector<DisplayLuts::LayerLut> tmpLayerLuts;
+    const auto error = mComposer.getRequestedLuts(mId, &tmpLayerLuts);
+    for (DisplayLuts::LayerLut& layerLut : tmpLayerLuts) {
+        if (layerLut.lut.pfd.get() >= 0) {
+            outLayerLuts->push_back({layerLut.layer,
+                                     Lut{ndk::ScopedFileDescriptor(layerLut.lut.pfd.release()),
+                                         layerLut.lut.lutProperties}});
+        }
+    }
+    return static_cast<Error>(error);
+}
+
 Error Display::getDisplayDecorationSupport(
         std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                 support) {
@@ -660,6 +675,11 @@
         }
     });
 }
+
+void Display::setPhysicalSizeInMm(std::optional<ui::Size> size) {
+    mPhysicalSize = size;
+}
+
 } // namespace impl
 
 // Layer methods
@@ -1037,6 +1057,14 @@
     return static_cast<Error>(intError);
 }
 
+Error Layer::setLuts(std::vector<Lut>& luts) {
+    if (CC_UNLIKELY(!mDisplay)) {
+        return Error::BAD_DISPLAY;
+    }
+    const auto intError = mComposer.setLayerLuts(mDisplay->getId(), mId, luts);
+    return static_cast<Error>(intError);
+}
+
 } // namespace impl
 } // namespace HWC2
 } // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index 5b94831..8e2aeaf 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -45,6 +45,7 @@
 #include <aidl/android/hardware/graphics/composer3/Color.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/Lut.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 #include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
 
@@ -66,6 +67,7 @@
 
 namespace hal = android::hardware::graphics::composer::hal;
 
+using aidl::android::hardware::drm::HdcpLevels;
 using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 
@@ -84,6 +86,7 @@
     virtual void onComposerHalSeamlessPossible(hal::HWDisplayId) = 0;
     virtual void onComposerHalVsyncIdle(hal::HWDisplayId) = 0;
     virtual void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) = 0;
+    virtual void onComposerHalHdcpLevelsChanged(hal::HWDisplayId, const HdcpLevels& levels) = 0;
 
 protected:
     ~ComposerCallback() = default;
@@ -102,6 +105,7 @@
     virtual bool isVsyncPeriodSwitchSupported() const = 0;
     virtual bool hasDisplayIdleTimerCapability() const = 0;
     virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
+    virtual std::optional<ui::Size> getPhysicalSizeInMm() const = 0;
 
     [[nodiscard]] virtual hal::Error acceptChanges() = 0;
     [[nodiscard]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
@@ -178,6 +182,9 @@
     [[nodiscard]] virtual hal::Error getClientTargetProperty(
             aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
                     outClientTargetProperty) = 0;
+    [[nodiscard]] virtual hal::Error getRequestedLuts(
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
+                    outLuts) = 0;
     [[nodiscard]] virtual hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) = 0;
@@ -261,6 +268,9 @@
     hal::Error getClientTargetProperty(
             aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness*
                     outClientTargetProperty) override;
+    hal::Error getRequestedLuts(
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*
+                    outLuts) override;
     hal::Error getDisplayDecorationSupport(
             std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
                     support) override;
@@ -276,6 +286,8 @@
     bool hasDisplayIdleTimerCapability() const override;
     void onLayerDestroyed(hal::HWLayerId layerId) override;
     hal::Error getPhysicalDisplayOrientation(Hwc2::AidlTransform* outTransform) const override;
+    void setPhysicalSizeInMm(std::optional<ui::Size> size);
+    std::optional<ui::Size> getPhysicalSizeInMm() const override { return mPhysicalSize; }
 
 private:
     void loadDisplayCapabilities();
@@ -309,6 +321,8 @@
     std::optional<
             std::unordered_set<aidl::android::hardware::graphics::composer3::DisplayCapability>>
             mDisplayCapabilities GUARDED_BY(mDisplayCapabilitiesMutex);
+    // Physical size in mm.
+    std::optional<ui::Size> mPhysicalSize;
 };
 
 } // namespace impl
@@ -354,6 +368,8 @@
     // AIDL HAL
     [[nodiscard]] virtual hal::Error setBrightness(float brightness) = 0;
     [[nodiscard]] virtual hal::Error setBlockingRegion(const android::Region& region) = 0;
+    [[nodiscard]] virtual hal::Error setLuts(
+            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) = 0;
 };
 
 namespace impl {
@@ -404,6 +420,8 @@
     // AIDL HAL
     hal::Error setBrightness(float brightness) override;
     hal::Error setBlockingRegion(const android::Region& region) override;
+    hal::Error setLuts(
+            std::vector<aidl::android::hardware::graphics::composer3::Lut>& luts) override;
 
 private:
     // These are references to data owned by HWComposer, which will outlive
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 776bcd3..d08e261 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -28,16 +28,15 @@
 #include "HWComposer.h"
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <compositionengine/Output.h>
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <ui/DebugUtils.h>
 #include <ui/GraphicBuffer.h>
 #include <utils/Errors.h>
-#include <utils/Trace.h>
 
 #include "../Layer.h" // needed only for debugging
 #include "../SurfaceFlingerProperties.h"
@@ -77,9 +76,8 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
-using aidl::android::hardware::graphics::composer3::VrrConfig;
+using aidl::android::hardware::graphics::composer3::DisplayConfiguration;
 using namespace std::string_literals;
-namespace hal = android::hardware::graphics::composer::hal;
 
 namespace android {
 
@@ -180,8 +178,8 @@
         displayData.lastPresentTimestamp = timestamp;
     }
 
-    ATRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
-               displayData.vsyncTraceToggle);
+    SFTRACE_INT(ftl::Concat("HW_VSYNC_", displayIdOpt->value).c_str(),
+                displayData.vsyncTraceToggle);
     displayData.vsyncTraceToggle = !displayData.vsyncTraceToggle;
 
     return displayIdOpt;
@@ -225,8 +223,8 @@
     return true;
 }
 
-void HWComposer::allocatePhysicalDisplay(hal::HWDisplayId hwcDisplayId,
-                                         PhysicalDisplayId displayId) {
+void HWComposer::allocatePhysicalDisplay(hal::HWDisplayId hwcDisplayId, PhysicalDisplayId displayId,
+                                         std::optional<ui::Size> physicalSize) {
     mPhysicalDisplayIdMap[hwcDisplayId] = displayId;
 
     if (!mPrimaryHwcDisplayId) {
@@ -238,6 +236,7 @@
             std::make_unique<HWC2::impl::Display>(*mComposer.get(), mCapabilities, hwcDisplayId,
                                                   hal::DisplayType::PHYSICAL);
     newDisplay->setConnected(true);
+    newDisplay->setPhysicalSizeInMm(physicalSize);
     displayData.hwcDisplay = std::move(newDisplay);
 }
 
@@ -279,6 +278,47 @@
     return getModesFromLegacyDisplayConfigs(hwcDisplayId);
 }
 
+DisplayConfiguration::Dpi HWComposer::getEstimatedDotsPerInchFromSize(
+        uint64_t hwcDisplayId, const HWCDisplayMode& hwcMode) const {
+    if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+        return {-1, -1};
+    }
+    if (const auto displayId = toPhysicalDisplayId(hwcDisplayId)) {
+        if (const auto it = mDisplayData.find(displayId.value());
+            it != mDisplayData.end() && it->second.hwcDisplay->getPhysicalSizeInMm()) {
+            ui::Size size = it->second.hwcDisplay->getPhysicalSizeInMm().value();
+            if (hwcMode.width > 0 && hwcMode.height > 0 && size.width > 0 && size.height > 0) {
+                static constexpr float kMmPerInch = 25.4f;
+                return {hwcMode.width * kMmPerInch / size.width,
+                        hwcMode.height * kMmPerInch / size.height};
+            }
+        }
+    }
+    return {-1, -1};
+}
+
+DisplayConfiguration::Dpi HWComposer::correctedDpiIfneeded(
+        DisplayConfiguration::Dpi dpi, DisplayConfiguration::Dpi estimatedDpi) const {
+    // hwc can be unreliable when it comes to dpi. A rough estimated dpi may yield better
+    // results. For instance, libdrm and bad edid may result in a dpi of {350, 290} for a
+    // 16:9 3840x2160 display, which would match a 4:3 aspect ratio.
+    // The logic here checks if hwc was able to provide some dpi, and if so if the dpi
+    // disparity between the axes is more reasonable than a rough estimate, otherwise use
+    // the estimated dpi as a corrected value.
+    if (estimatedDpi.x == -1 || estimatedDpi.y == -1) {
+        return dpi;
+    }
+    if (dpi.x == -1 || dpi.y == -1) {
+        return estimatedDpi;
+    }
+    if (std::min(dpi.x, dpi.y) != 0 && std::min(estimatedDpi.x, estimatedDpi.y) != 0 &&
+        abs(dpi.x - dpi.y) / std::min(dpi.x, dpi.y) >
+                abs(estimatedDpi.x - estimatedDpi.y) / std::min(estimatedDpi.x, estimatedDpi.y)) {
+        return estimatedDpi;
+    }
+    return dpi;
+}
+
 std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromDisplayConfigurations(
         uint64_t hwcDisplayId, int32_t maxFrameIntervalNs) const {
     std::vector<hal::DisplayConfiguration> configs;
@@ -297,9 +337,16 @@
                                       .configGroup = config.configGroup,
                                       .vrrConfig = config.vrrConfig};
 
+        const DisplayConfiguration::Dpi estimatedDPI =
+                getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
         if (config.dpi) {
-            hwcMode.dpiX = config.dpi->x;
-            hwcMode.dpiY = config.dpi->y;
+            const DisplayConfiguration::Dpi dpi =
+                    correctedDpiIfneeded(config.dpi.value(), estimatedDPI);
+            hwcMode.dpiX = dpi.x;
+            hwcMode.dpiY = dpi.y;
+        } else if (estimatedDPI.x != -1 && estimatedDPI.y != -1) {
+            hwcMode.dpiX = estimatedDPI.x;
+            hwcMode.dpiY = estimatedDPI.y;
         }
 
         if (!mEnableVrrTimeout) {
@@ -331,12 +378,14 @@
 
         const int32_t dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X);
         const int32_t dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y);
-        if (dpiX != -1) {
-            hwcMode.dpiX = static_cast<float>(dpiX) / 1000.f;
-        }
-        if (dpiY != -1) {
-            hwcMode.dpiY = static_cast<float>(dpiY) / 1000.f;
-        }
+        const DisplayConfiguration::Dpi hwcDpi =
+                DisplayConfiguration::Dpi{dpiX == -1 ? dpiY : dpiX / 1000.f,
+                                          dpiY == -1 ? dpiY : dpiY / 1000.f};
+        const DisplayConfiguration::Dpi estimatedDPI =
+                getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
+        const DisplayConfiguration::Dpi dpi = correctedDpiIfneeded(hwcDpi, estimatedDPI);
+        hwcMode.dpiX = dpi.x;
+        hwcMode.dpiY = dpi.y;
 
         modes.push_back(hwcMode);
     }
@@ -430,14 +479,14 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     auto error = displayData.hwcDisplay->setVsyncEnabled(enabled);
     RETURN_IF_HWC_ERROR(error, displayId);
 
     displayData.vsyncEnabled = enabled;
 
-    ATRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
-               enabled == hal::Vsync::ENABLE ? 1 : 0);
+    SFTRACE_INT(ftl::Concat("HW_VSYNC_ON_", displayId.value).c_str(),
+                enabled == hal::Vsync::ENABLE ? 1 : 0);
 }
 
 status_t HWComposer::setClientTarget(HalDisplayId displayId, uint32_t slot,
@@ -457,7 +506,7 @@
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
         nsecs_t expectedPresentTime, Fps frameInterval,
         std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
@@ -495,7 +544,7 @@
     }();
 
     displayData.validateWasSkipped = false;
-    ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
+    SFTRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
     if (canSkipValidate) {
         sp<Fence> outPresentFence = Fence::NO_FENCE;
         uint32_t state = UINT32_MAX;
@@ -536,6 +585,7 @@
 
     DeviceRequestedChanges::ClientTargetProperty clientTargetProperty;
     error = hwcDisplay->getClientTargetProperty(&clientTargetProperty);
+    RETURN_IF_HWC_ERROR_FOR("getClientTargetProperty", error, displayId, BAD_INDEX);
 
     outChanges->emplace(DeviceRequestedChanges{std::move(changedTypes), std::move(displayRequests),
                                                std::move(layerRequests),
@@ -570,7 +620,7 @@
 status_t HWComposer::presentAndGetReleaseFences(
         HalDisplayId displayId,
         std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
@@ -586,7 +636,7 @@
     }
 
     if (earliestPresentTime) {
-        ATRACE_NAME("wait for earliest present time");
+        SFTRACE_NAME("wait for earliest present time");
         std::this_thread::sleep_until(*earliestPresentTime);
     }
 
@@ -602,6 +652,13 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::executeCommands(HalDisplayId displayId) {
+    auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
+    auto error = static_cast<hal::Error>(mComposer->executeCommands(hwcDisplay->getId()));
+    RETURN_IF_HWC_ERROR_FOR("executeCommands", error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 status_t HWComposer::setPowerMode(PhysicalDisplayId displayId, hal::PowerMode mode) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
 
@@ -892,9 +949,9 @@
 status_t HWComposer::notifyExpectedPresent(PhysicalDisplayId displayId,
                                            TimePoint expectedPresentTime, Fps frameInterval) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
-    ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
-                  ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
-                  ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+    SFTRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+                   ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+                   ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
     const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
                                                         expectedPresentTime.ns(),
                                                         frameInterval.getPeriodNsecs());
@@ -921,6 +978,21 @@
     return NO_ERROR;
 }
 
+status_t HWComposer::getRequestedLuts(
+        PhysicalDisplayId displayId,
+        std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>* outLuts) {
+    RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+    const auto error = mDisplayData[displayId].hwcDisplay->getRequestedLuts(outLuts);
+    if (error == hal::Error::UNSUPPORTED) {
+        RETURN_IF_HWC_ERROR(error, displayId, INVALID_OPERATION);
+    }
+    if (error == hal::Error::BAD_PARAMETER) {
+        RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
+    }
+    RETURN_IF_HWC_ERROR(error, displayId, UNKNOWN_ERROR);
+    return NO_ERROR;
+}
+
 status_t HWComposer::setAutoLowLatencyMode(PhysicalDisplayId displayId, bool on) {
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto error = mDisplayData[displayId].hwcDisplay->setAutoLowLatencyMode(on);
@@ -964,8 +1036,45 @@
     return mSupportedLayerGenericMetadata;
 }
 
+void HWComposer::dumpOverlayProperties(std::string& result) const {
+    // dump overlay properties
+    result.append("OverlayProperties:\n");
+    base::StringAppendF(&result, "supportMixedColorSpaces: %d\n",
+                        mOverlayProperties.supportMixedColorSpaces);
+    base::StringAppendF(&result, "SupportedBufferCombinations(%zu entries)\n",
+                        mOverlayProperties.combinations.size());
+    for (const auto& combination : mOverlayProperties.combinations) {
+        result.append("    pixelFormats=\n");
+        for (const auto& pixelFormat : combination.pixelFormats) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodePixelFormat(static_cast<PixelFormat>(pixelFormat)).c_str(),
+                                static_cast<uint32_t>(pixelFormat));
+        }
+        result.append("    standards=\n");
+        for (const auto& standard : combination.standards) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeStandardOnly(static_cast<uint32_t>(standard)).c_str(),
+                                static_cast<uint32_t>(standard));
+        }
+        result.append("    transfers=\n");
+        for (const auto& transfer : combination.transfers) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeTransferOnly(static_cast<uint32_t>(transfer)).c_str(),
+                                static_cast<uint32_t>(transfer));
+        }
+        result.append("    ranges=\n");
+        for (const auto& range : combination.ranges) {
+            base::StringAppendF(&result, "        %s (%d)\n",
+                                decodeRangeOnly(static_cast<uint32_t>(range)).c_str(),
+                                static_cast<uint32_t>(range));
+        }
+        result.append("\n");
+    }
+}
+
 void HWComposer::dump(std::string& result) const {
     result.append(mComposer->dumpDebugInfo());
+    dumpOverlayProperties(result);
 }
 
 std::optional<PhysicalDisplayId> HWComposer::toPhysicalDisplayId(
@@ -1015,6 +1124,8 @@
             getDisplayIdentificationData(hwcDisplayId, &port, &data);
             if (auto newInfo = parseDisplayIdentificationData(port, data)) {
                 info->deviceProductInfo = std::move(newInfo->deviceProductInfo);
+                info->preferredDetailedTimingDescriptor =
+                        std::move(newInfo->preferredDetailedTimingDescriptor);
             } else {
                 ALOGE("Failed to parse identification data for display %" PRIu64, hwcDisplayId);
             }
@@ -1057,7 +1168,11 @@
     }
 
     if (!isConnected(info->id)) {
-        allocatePhysicalDisplay(hwcDisplayId, info->id);
+        std::optional<ui::Size> size = std::nullopt;
+        if (info->preferredDetailedTimingDescriptor) {
+            size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        }
+        allocatePhysicalDisplay(hwcDisplayId, info->id, size);
     }
     return info;
 }
@@ -1107,7 +1222,7 @@
 
 status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
                                          std::chrono::milliseconds timeout) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
     const auto error = mDisplayData[displayId].hwcDisplay->setIdleTimerEnabled(timeout);
     if (error == hal::Error::UNSUPPORTED) {
@@ -1126,7 +1241,7 @@
 }
 
 Hwc2::AidlTransform HWComposer::getPhysicalDisplayOrientation(PhysicalDisplayId displayId) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     RETURN_IF_INVALID_DISPLAY(displayId, Hwc2::AidlTransform::NONE);
     Hwc2::AidlTransform outTransform;
     const auto& hwcDisplay = mDisplayData.at(displayId).hwcDisplay;
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 7fbbb1a..b95c619 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -52,6 +52,7 @@
 #include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 #include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayLuts.h>
 #include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
 
 namespace android {
@@ -133,7 +134,8 @@
     // supported by the HWC can be queried in advance, but allocation may fail for other reasons.
     virtual bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) = 0;
 
-    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) = 0;
+    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+                                         std::optional<ui::Size> physicalSize) = 0;
 
     // Attempts to create a new layer on this display
     virtual std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) = 0;
@@ -160,6 +162,8 @@
             HalDisplayId,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) = 0;
 
+    virtual status_t executeCommands(HalDisplayId) = 0;
+
     // set power mode
     virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0;
 
@@ -269,6 +273,8 @@
 
     virtual void dump(std::string& out) const = 0;
 
+    virtual void dumpOverlayProperties(std::string& out) const = 0;
+
     virtual Hwc2::Composer* getComposer() const = 0;
 
     // Returns the first display connected at boot. Its connection via HWComposer::onHotplug,
@@ -305,6 +311,11 @@
     virtual status_t setRefreshRateChangedCallbackDebugEnabled(PhysicalDisplayId, bool enabled) = 0;
     virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
                                            Fps frameInterval) = 0;
+
+    // Composer 4.0
+    virtual status_t getRequestedLuts(
+            PhysicalDisplayId,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -339,7 +350,8 @@
     bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) override;
 
     // Called from SurfaceFlinger, when the state for a new physical display needs to be recreated.
-    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) override;
+    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+                                 std::optional<ui::Size> physicalSize) override;
 
     // Attempts to create a new layer on this display
     std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) override;
@@ -359,6 +371,8 @@
             HalDisplayId,
             std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) override;
 
+    status_t executeCommands(HalDisplayId) override;
+
     // set power mode
     status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override;
 
@@ -466,8 +480,15 @@
     status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
                                    Fps frameInterval) override;
 
+    // Composer 4.0
+    status_t getRequestedLuts(
+            PhysicalDisplayId,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*)
+            override;
+
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
+    void dumpOverlayProperties(std::string& out) const override;
 
     Hwc2::Composer* getComposer() const override { return mComposer.get(); }
 
@@ -513,6 +534,13 @@
     std::optional<DisplayIdentificationInfo> onHotplugDisconnect(hal::HWDisplayId);
     bool shouldIgnoreHotplugConnect(hal::HWDisplayId, bool hasDisplayIdentificationData) const;
 
+    aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi
+    getEstimatedDotsPerInchFromSize(uint64_t hwcDisplayId, const HWCDisplayMode& hwcMode) const;
+
+    aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi correctedDpiIfneeded(
+            aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi dpi,
+            aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi estimatedDpi)
+            const;
     std::vector<HWCDisplayMode> getModesFromDisplayConfigurations(uint64_t hwcDisplayId,
                                                                   int32_t maxFrameIntervalNs) const;
     std::vector<HWCDisplayMode> getModesFromLegacyDisplayConfigs(uint64_t hwcDisplayId) const;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index 12ab2c2..ee1e07a 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -27,11 +27,11 @@
 #include <SurfaceFlingerProperties.h>
 #include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
 #include <android/binder_manager.h>
+#include <common/trace.h>
 #include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
 #include <hidl/HidlTransportSupport.h>
 #include <hidl/HidlTransportUtils.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include "HWC2.h"
 #include "Hal.h"
@@ -46,6 +46,8 @@
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
 using aidl::android::hardware::graphics::composer3::DimmingStage;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::DisplayLuts;
+using aidl::android::hardware::graphics::composer3::Lut;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
 
 namespace android {
@@ -209,7 +211,7 @@
     if (!buffer || buffer->initCheck() != ::android::OK) {
         return nullptr;
     }
-    return std::move(buffer);
+    return buffer;
 }
 
 } // anonymous namespace
@@ -588,7 +590,7 @@
 }
 
 Error HidlComposer::presentDisplay(Display display, int* outPresentFence) {
-    ATRACE_NAME("HwcPresentDisplay");
+    SFTRACE_NAME("HwcPresentDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentDisplay();
 
@@ -676,7 +678,7 @@
 Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
                                     int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
                                     uint32_t* outNumRequests) {
-    ATRACE_NAME("HwcValidateDisplay");
+    SFTRACE_NAME("HwcValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.validateDisplay();
 
@@ -694,7 +696,7 @@
                                              int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
                                              uint32_t* outNumRequests, int* outPresentFence,
                                              uint32_t* state) {
-    ATRACE_NAME("HwcPresentOrValidateDisplay");
+    SFTRACE_NAME("HwcPresentOrValidateDisplay");
     mWriter.selectDisplay(display);
     mWriter.presentOrvalidateDisplay();
 
@@ -1408,6 +1410,14 @@
     return Error::NONE;
 }
 
+Error HidlComposer::getRequestedLuts(Display, std::vector<DisplayLuts::LayerLut>*) {
+    return Error::NONE;
+}
+
+Error HidlComposer::setLayerLuts(Display, Layer, std::vector<Lut>&) {
+    return Error::NONE;
+}
+
 Error HidlComposer::setLayerBrightness(Display, Layer, float) {
     return Error::NONE;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index d78bfb7..701a54b 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -351,6 +351,12 @@
                                    Hdr*) override;
     Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
     Error notifyExpectedPresent(Display, nsecs_t, int32_t) override;
+    Error getRequestedLuts(
+            Display,
+            std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*)
+            override;
+    Error setLayerLuts(Display, Layer,
+                       std::vector<aidl::android::hardware::graphics::composer3::Lut>&) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
index a0c943b..334c104 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -27,9 +27,9 @@
 #include <optional>
 
 #include <android-base/properties.h>
+#include <common/trace.h>
 #include <utils/Log.h>
 #include <utils/Mutex.h>
-#include <utils/Trace.h>
 
 #include <binder/IServiceManager.h>
 
@@ -46,9 +46,21 @@
 namespace impl {
 
 using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::ChannelConfig;
 using aidl::android::hardware::power::Mode;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionTag;
 using aidl::android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::WorkDurationFixedV1;
+
+using aidl::android::hardware::common::fmq::MQDescriptor;
+using aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using aidl::android::hardware::power::ChannelMessage;
+using android::hardware::EventFlag;
+
+using ChannelMessageContents = ChannelMessage::ChannelMessageContents;
+using MsgQueue = android::AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>;
+using FlagQueue = android::AidlMessageQueue<int8_t, SynchronizedReadWrite>;
 
 PowerAdvisor::~PowerAdvisor() = default;
 
@@ -62,9 +74,9 @@
 
 void traceExpensiveRendering(bool enabled) {
     if (enabled) {
-        ATRACE_ASYNC_BEGIN("ExpensiveRendering", 0);
+        SFTRACE_ASYNC_BEGIN("ExpensiveRendering", 0);
     } else {
-        ATRACE_ASYNC_END("ExpensiveRendering", 0);
+        SFTRACE_ASYNC_END("ExpensiveRendering", 0);
     }
 }
 
@@ -139,15 +151,7 @@
     if (!mBootFinished.load()) {
         return;
     }
-    if (usePowerHintSession()) {
-        std::lock_guard lock(mHintSessionMutex);
-        if (ensurePowerHintSessionRunning()) {
-            auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP);
-            if (!ret.isOk()) {
-                mHintSession = nullptr;
-            }
-        }
-    }
+    sendHintSessionHint(SessionHint::CPU_LOAD_UP);
 }
 
 void PowerAdvisor::notifyDisplayUpdateImminentAndCpuReset() {
@@ -159,15 +163,7 @@
 
     if (mSendUpdateImminent.exchange(false)) {
         ALOGV("AIDL notifyDisplayUpdateImminentAndCpuReset");
-        if (usePowerHintSession()) {
-            std::lock_guard lock(mHintSessionMutex);
-            if (ensurePowerHintSessionRunning()) {
-                auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
-                if (!ret.isOk()) {
-                    mHintSession = nullptr;
-                }
-            }
-        }
+        sendHintSessionHint(SessionHint::CPU_LOAD_RESET);
 
         if (!mHasDisplayUpdateImminent) {
             ALOGV("Skipped sending DISPLAY_UPDATE_IMMINENT because HAL doesn't support it");
@@ -204,36 +200,119 @@
     return *mSupportsHintSession;
 }
 
+bool PowerAdvisor::shouldCreateSessionWithConfig() {
+    return mSessionConfigSupported && mBootFinished &&
+            FlagManager::getInstance().adpf_use_fmq_channel();
+}
+
+void PowerAdvisor::sendHintSessionHint(SessionHint hint) {
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Power hint session is not enabled, skip sending session hint");
+        return;
+    }
+    SFTRACE_CALL();
+    if (sTraceHintSessionData) SFTRACE_INT("Session hint", static_cast<int>(hint));
+    {
+        std::scoped_lock lock(mHintSessionMutex);
+        if (!ensurePowerHintSessionRunning()) {
+            ALOGV("Hint session not running and could not be started, skip sending session hint");
+            return;
+        }
+        ALOGV("Sending session hint: %d", static_cast<int>(hint));
+        if (!writeHintSessionMessage<ChannelMessageContents::Tag::hint>(&hint, 1)) {
+            auto ret = mHintSession->sendHint(hint);
+            if (!ret.isOk()) {
+                ALOGW("Failed to send session hint with error: %s", ret.errorMessage());
+                mHintSession = nullptr;
+            }
+        }
+    }
+}
+
 bool PowerAdvisor::ensurePowerHintSessionRunning() {
     if (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
-        auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
-                                                   mHintSessionThreadIds, mTargetDuration.ns());
-
-        if (ret.isOk()) {
-            mHintSession = ret.value();
+        if (shouldCreateSessionWithConfig()) {
+            auto ret = getPowerHal().createHintSessionWithConfig(getpid(),
+                                                                 static_cast<int32_t>(getuid()),
+                                                                 mHintSessionThreadIds,
+                                                                 mTargetDuration.ns(),
+                                                                 SessionTag::SURFACEFLINGER,
+                                                                 &mSessionConfig);
+            if (ret.isOk()) {
+                mHintSession = ret.value();
+                if (FlagManager::getInstance().adpf_use_fmq_channel_fixed()) {
+                    setUpFmq();
+                }
+            }
+            // If it fails the first time we try, or ever returns unsupported, assume unsupported
+            else if (mFirstConfigSupportCheck || ret.isUnsupported()) {
+                ALOGI("Hint session with config is unsupported, falling back to a legacy session");
+                mSessionConfigSupported = false;
+            }
+            mFirstConfigSupportCheck = false;
+        }
+        // Immediately try original method after, in case the first way returned unsupported
+        if (mHintSession == nullptr && !shouldCreateSessionWithConfig()) {
+            auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+                                                       mHintSessionThreadIds, mTargetDuration.ns());
+            if (ret.isOk()) {
+                mHintSession = ret.value();
+            }
         }
     }
     return mHintSession != nullptr;
 }
 
-void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
-    if (!usePowerHintSession()) {
-        ALOGV("Power hint session target duration cannot be set, skipping");
+void PowerAdvisor::setUpFmq() {
+    auto&& channelRet = getPowerHal().getSessionChannel(getpid(), static_cast<int32_t>(getuid()));
+    if (!channelRet.isOk()) {
+        ALOGE("Failed to get session channel with error: %s", channelRet.errorMessage());
         return;
     }
-    ATRACE_CALL();
+    auto& channelConfig = channelRet.value();
+    mMsgQueue = std::make_unique<MsgQueue>(std::move(channelConfig.channelDescriptor), true);
+    LOG_ALWAYS_FATAL_IF(!mMsgQueue->isValid(), "Failed to set up hint session msg queue");
+    LOG_ALWAYS_FATAL_IF(channelConfig.writeFlagBitmask <= 0,
+                        "Invalid flag bit masks found in channel config: writeBitMask(%d)",
+                        channelConfig.writeFlagBitmask);
+    mFmqWriteMask = static_cast<uint32_t>(channelConfig.writeFlagBitmask);
+    if (!channelConfig.eventFlagDescriptor.has_value()) {
+        // For FMQ v1 in Android 15 we will force using shared event flag since the default
+        // no-op FMQ impl in Power HAL v5 will always return a valid channel config with
+        // non-zero masks but no shared flag.
+        mMsgQueue = nullptr;
+        ALOGE("No event flag descriptor found in channel config");
+        return;
+    }
+    mFlagQueue = std::make_unique<FlagQueue>(std::move(*channelConfig.eventFlagDescriptor), true);
+    LOG_ALWAYS_FATAL_IF(!mFlagQueue->isValid(), "Failed to set up hint session flag queue");
+    auto status = EventFlag::createEventFlag(mFlagQueue->getEventFlagWord(), &mEventFlag);
+    LOG_ALWAYS_FATAL_IF(status != OK, "Failed to set up hint session event flag");
+}
+
+void PowerAdvisor::updateTargetWorkDuration(Duration targetDuration) {
+    if (!mBootFinished || !usePowerHintSession()) {
+        ALOGV("Power hint session is not enabled, skipping target update");
+        return;
+    }
+    SFTRACE_CALL();
     {
         mTargetDuration = targetDuration;
-        if (sTraceHintSessionData) ATRACE_INT64("Time target", targetDuration.ns());
+        if (sTraceHintSessionData) SFTRACE_INT64("Time target", targetDuration.ns());
         if (targetDuration == mLastTargetDurationSent) return;
-        std::lock_guard lock(mHintSessionMutex);
-        if (ensurePowerHintSessionRunning()) {
-            ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
-            mLastTargetDurationSent = targetDuration;
+        std::scoped_lock lock(mHintSessionMutex);
+        if (!ensurePowerHintSessionRunning()) {
+            ALOGV("Hint session not running and could not be started, skip updating target");
+            return;
+        }
+        ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
+        mLastTargetDurationSent = targetDuration;
+        auto target = targetDuration.ns();
+        if (!writeHintSessionMessage<ChannelMessageContents::Tag::targetDuration>(&target, 1)) {
             auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
             if (!ret.isOk()) {
                 ALOGW("Failed to set power hint target work duration with error: %s",
-                      ret.getDescription().c_str());
+                      ret.errorMessage());
                 mHintSession = nullptr;
             }
         }
@@ -245,28 +324,31 @@
         ALOGV("Actual work duration power hint cannot be sent, skipping");
         return;
     }
-    ATRACE_CALL();
-    std::optional<Duration> actualDuration = estimateWorkDuration();
-    if (!actualDuration.has_value() || actualDuration < 0ns) {
+    SFTRACE_CALL();
+    std::optional<WorkDuration> actualDuration = estimateWorkDuration();
+    if (!actualDuration.has_value() || actualDuration->durationNanos < 0) {
         ALOGV("Failed to send actual work duration, skipping");
         return;
     }
-    actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
-    mActualDuration = actualDuration;
-
+    actualDuration->durationNanos += sTargetSafetyMargin.ns();
     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());
+        SFTRACE_INT64("Measured duration", actualDuration->durationNanos);
+        SFTRACE_INT64("Target error term", actualDuration->durationNanos - mTargetDuration.ns());
+        SFTRACE_INT64("Reported duration", actualDuration->durationNanos);
+        if (supportsGpuReporting()) {
+            SFTRACE_INT64("Reported cpu duration", actualDuration->cpuDurationNanos);
+            SFTRACE_INT64("Reported gpu duration", actualDuration->gpuDurationNanos);
+        }
+        SFTRACE_INT64("Reported target", mLastTargetDurationSent.ns());
+        SFTRACE_INT64("Reported target error term",
+                      actualDuration->durationNanos - mLastTargetDurationSent.ns());
     }
 
-    ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
-          " with error: %" PRId64,
-          actualDuration->ns(), mLastTargetDurationSent.ns(),
-          Duration{*actualDuration - mLastTargetDurationSent}.ns());
+    ALOGV("Sending actual work duration of: %" PRId64 " with cpu: %" PRId64 " and gpu: %" PRId64
+          " on reported target: %" PRId64 " with error: %" PRId64,
+          actualDuration->durationNanos, actualDuration->cpuDurationNanos,
+          actualDuration->gpuDurationNanos, mLastTargetDurationSent.ns(),
+          actualDuration->durationNanos - mLastTargetDurationSent.ns());
 
     if (mTimingTestingMode) {
         mDelayReportActualMutexAcquisitonPromise.get_future().wait();
@@ -274,34 +356,73 @@
     }
 
     {
-        std::lock_guard lock(mHintSessionMutex);
+        std::scoped_lock lock(mHintSessionMutex);
         if (!ensurePowerHintSessionRunning()) {
-            ALOGV("Hint session not running and could not be started, skipping");
+            ALOGV("Hint session not running and could not be started, skip reporting durations");
             return;
         }
-
-        WorkDuration duration{
-                .timeStampNanos = TimePoint::now().ns(),
-                // TODO(b/284324521): Correctly calculate total duration.
-                .durationNanos = actualDuration->ns(),
-                .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
-                .cpuDurationNanos = actualDuration->ns(),
-                // TODO(b/284324521): Calculate RenderEngine GPU time.
-                .gpuDurationNanos = 0,
-        };
-        mHintSessionQueue.push_back(duration);
-
-        auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
-        if (!ret.isOk()) {
-            ALOGW("Failed to report actual work durations with error: %s",
-                  ret.getDescription().c_str());
-            mHintSession = nullptr;
-            return;
+        mHintSessionQueue.push_back(*actualDuration);
+        if (!writeHintSessionMessage<
+                    ChannelMessageContents::Tag::workDuration>(mHintSessionQueue.data(),
+                                                               mHintSessionQueue.size())) {
+            auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
+            if (!ret.isOk()) {
+                ALOGW("Failed to report actual work durations with error: %s", ret.errorMessage());
+                mHintSession = nullptr;
+                return;
+            }
         }
     }
     mHintSessionQueue.clear();
 }
 
+template <ChannelMessage::ChannelMessageContents::Tag T, class In>
+bool PowerAdvisor::writeHintSessionMessage(In* contents, size_t count) {
+    if (!mMsgQueue) {
+        ALOGV("Skip using FMQ with message tag %hhd as it's not supported", T);
+        return false;
+    }
+    auto availableSize = mMsgQueue->availableToWrite();
+    if (availableSize < count) {
+        ALOGW("Skip using FMQ with message tag %hhd as there isn't enough space", T);
+        return false;
+    }
+    MsgQueue::MemTransaction tx;
+    if (!mMsgQueue->beginWrite(count, &tx)) {
+        ALOGW("Failed to begin writing message with tag %hhd", T);
+        return false;
+    }
+    for (size_t i = 0; i < count; ++i) {
+        if constexpr (T == ChannelMessageContents::Tag::workDuration) {
+            const WorkDuration& duration = contents[i];
+            new (tx.getSlot(i)) ChannelMessage{
+                    .sessionID = static_cast<int32_t>(mSessionConfig.id),
+                    .timeStampNanos =
+                            (i == count - 1) ? ::android::uptimeNanos() : duration.timeStampNanos,
+                    .data = ChannelMessageContents::make<ChannelMessageContents::Tag::workDuration,
+                                                         WorkDurationFixedV1>({
+                            .durationNanos = duration.durationNanos,
+                            .workPeriodStartTimestampNanos = duration.workPeriodStartTimestampNanos,
+                            .cpuDurationNanos = duration.cpuDurationNanos,
+                            .gpuDurationNanos = duration.gpuDurationNanos,
+                    }),
+            };
+        } else {
+            new (tx.getSlot(i)) ChannelMessage{
+                    .sessionID = static_cast<int32_t>(mSessionConfig.id),
+                    .timeStampNanos = ::android::uptimeNanos(),
+                    .data = ChannelMessageContents::make<T, In>(std::move(contents[i])),
+            };
+        }
+    }
+    if (!mMsgQueue->commitWrite(count)) {
+        ALOGW("Failed to send message with tag %hhd, fall back to binder call", T);
+        return false;
+    }
+    mEventFlag->wake(mFmqWriteMask);
+    return true;
+}
+
 void PowerAdvisor::enablePowerHintSession(bool enabled) {
     mHintSessionEnabled = enabled;
 }
@@ -317,19 +438,50 @@
     }
     LOG_ALWAYS_FATAL_IF(mHintSessionThreadIds.empty(),
                         "No thread IDs provided to power hint session!");
-    std::lock_guard lock(mHintSessionMutex);
-    if (mHintSession != nullptr) {
-        ALOGE("Cannot start power hint session: already running");
-        return false;
+    {
+        std::scoped_lock lock(mHintSessionMutex);
+        if (mHintSession != nullptr) {
+            ALOGE("Cannot start power hint session: already running");
+            return false;
+        }
+        return ensurePowerHintSessionRunning();
     }
-    return ensurePowerHintSessionRunning();
 }
 
-void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+bool PowerAdvisor::supportsGpuReporting() {
+    return mBootFinished && FlagManager::getInstance().adpf_gpu_sf();
+}
+
+void PowerAdvisor::setGpuStartTime(DisplayId displayId, TimePoint startTime) {
     DisplayTimingData& displayData = mDisplayTimingData[displayId];
     if (displayData.gpuEndFenceTime) {
         nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
         if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
+            for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
+                if (!otherDisplayData.lastValidGpuStartTime.has_value() ||
+                    !otherDisplayData.lastValidGpuEndTime.has_value())
+                    continue;
+                if ((*otherDisplayData.lastValidGpuStartTime < *displayData.gpuStartTime) &&
+                    (*otherDisplayData.lastValidGpuEndTime > *displayData.gpuStartTime)) {
+                    displayData.lastValidGpuStartTime = *otherDisplayData.lastValidGpuEndTime;
+                    break;
+                }
+            }
+        }
+        displayData.gpuEndFenceTime = nullptr;
+    }
+    displayData.gpuStartTime = startTime;
+}
+
+void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
+    DisplayTimingData& displayData = mDisplayTimingData[displayId];
+    if (displayData.gpuEndFenceTime && !supportsGpuReporting()) {
+        nsecs_t signalTime = displayData.gpuEndFenceTime->getSignalTime();
+        if (signalTime != Fence::SIGNAL_TIME_INVALID && signalTime != Fence::SIGNAL_TIME_PENDING) {
+            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
+            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
             for (auto&& [_, otherDisplayData] : mDisplayTimingData) {
                 // If the previous display started before us but ended after we should have
                 // started, then it likely delayed our start time and we must compensate for that.
@@ -342,12 +494,12 @@
                     break;
                 }
             }
-            displayData.lastValidGpuStartTime = displayData.gpuStartTime;
-            displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
         }
     }
     displayData.gpuEndFenceTime = std::move(fenceTime);
-    displayData.gpuStartTime = TimePoint::now();
+    if (!supportsGpuReporting()) {
+        displayData.gpuStartTime = TimePoint::now();
+    }
 }
 
 void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
@@ -368,9 +520,8 @@
     mDisplayTimingData[displayId].skippedValidate = skipped;
 }
 
-void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
-                                                bool requiresClientComposition) {
-    mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
+void PowerAdvisor::setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) {
+    mDisplayTimingData[displayId].requiresRenderEngine = requiresRenderEngine;
 }
 
 void PowerAdvisor::setExpectedPresentTime(TimePoint expectedPresentTime) {
@@ -378,8 +529,8 @@
 }
 
 void PowerAdvisor::setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) {
-    mLastSfPresentEndTime = presentEndTime;
     mLastPresentFenceTime = presentFenceTime;
+    mLastSfPresentEndTime = presentEndTime;
 }
 
 void PowerAdvisor::setFrameDelay(Duration frameDelayDuration) {
@@ -420,7 +571,7 @@
     return sortedDisplays;
 }
 
-std::optional<Duration> PowerAdvisor::estimateWorkDuration() {
+std::optional<WorkDuration> PowerAdvisor::estimateWorkDuration() {
     if (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull()) {
         return std::nullopt;
     }
@@ -439,11 +590,10 @@
     // used to accumulate gpu time as we iterate over the active displays
     std::optional<TimePoint> estimatedGpuEndTime;
 
-    // The timing info for the previously calculated display, if there was one
-    std::optional<DisplayTimeline> previousDisplayTiming;
     std::vector<DisplayId>&& displayIds =
             getOrderedDisplayIds(&DisplayTimingData::hwcPresentStartTime);
     DisplayTimeline displayTiming;
+    std::optional<GpuTimeline> firstGpuTimeline;
 
     // Iterate over the displays that use hwc in the same order they are presented
     for (DisplayId displayId : displayIds) {
@@ -455,14 +605,6 @@
 
         displayTiming = displayData.calculateDisplayTimeline(mLastPresentFenceTime);
 
-        // If this is the first display, include the duration before hwc present starts
-        if (!previousDisplayTiming.has_value()) {
-            estimatedHwcEndTime += displayTiming.hwcPresentStartTime - mCommitStartTimes[0];
-        } else { // Otherwise add the time since last display's hwc present finished
-            estimatedHwcEndTime +=
-                    displayTiming.hwcPresentStartTime - previousDisplayTiming->hwcPresentEndTime;
-        }
-
         // Update predicted present finish time with this display's present time
         estimatedHwcEndTime = displayTiming.hwcPresentEndTime;
 
@@ -477,6 +619,9 @@
         // Estimate the reference frame's gpu timing
         auto gpuTiming = displayData.estimateGpuTiming(previousValidGpuEndTime);
         if (gpuTiming.has_value()) {
+            if (!firstGpuTimeline.has_value()) {
+                firstGpuTimeline = gpuTiming;
+            }
             previousValidGpuEndTime = gpuTiming->startTime + gpuTiming->duration;
 
             // Estimate the prediction frame's gpu end time from the reference frame
@@ -484,9 +629,7 @@
                                            estimatedGpuEndTime.value_or(TimePoint{0ns})) +
                     gpuTiming->duration;
         }
-        previousDisplayTiming = displayTiming;
     }
-    ATRACE_INT64("Idle duration", idleDuration.ns());
 
     TimePoint estimatedFlingerEndTime = mLastSfPresentEndTime;
 
@@ -499,15 +642,33 @@
     Duration totalDuration = mFrameDelayDuration +
             std::max(estimatedHwcEndTime, estimatedGpuEndTime.value_or(TimePoint{0ns})) -
             mCommitStartTimes[0];
+    Duration totalDurationWithoutGpu =
+            mFrameDelayDuration + estimatedHwcEndTime - mCommitStartTimes[0];
 
     // We finish SurfaceFlinger when post-composition finishes, so add that in here
     Duration flingerDuration =
             estimatedFlingerEndTime + mLastPostcompDuration - mCommitStartTimes[0];
+    Duration estimatedGpuDuration = firstGpuTimeline.has_value()
+            ? estimatedGpuEndTime.value_or(TimePoint{0ns}) - firstGpuTimeline->startTime
+            : Duration::fromNs(0);
 
     // Combine the two timings into a single normalized one
     Duration combinedDuration = combineTimingEstimates(totalDuration, flingerDuration);
+    Duration cpuDuration = combineTimingEstimates(totalDurationWithoutGpu, flingerDuration);
 
-    return std::make_optional(combinedDuration);
+    WorkDuration duration{
+            .timeStampNanos = TimePoint::now().ns(),
+            .durationNanos = combinedDuration.ns(),
+            .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+            .cpuDurationNanos = supportsGpuReporting() ? cpuDuration.ns() : 0,
+            .gpuDurationNanos = supportsGpuReporting() ? estimatedGpuDuration.ns() : 0,
+    };
+    if (sTraceHintSessionData) {
+        SFTRACE_INT64("Idle duration", idleDuration.ns());
+        SFTRACE_INT64("Total duration", totalDuration.ns());
+        SFTRACE_INT64("Flinger duration", flingerDuration.ns());
+    }
+    return std::make_optional(duration);
 }
 
 Duration PowerAdvisor::combineTimingEstimates(Duration totalDuration, Duration flingerDuration) {
@@ -558,7 +719,7 @@
 
 std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
         std::optional<TimePoint> previousEndTime) {
-    if (!(usedClientComposition && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
+    if (!(requiresRenderEngine && lastValidGpuStartTime.has_value() && gpuEndFenceTime)) {
         return std::nullopt;
     }
     const TimePoint latestGpuStartTime =
diff --git a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
index bbe51cc0..1076b2b 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -29,6 +29,7 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 #include <aidl/android/hardware/power/IPower.h>
+#include <fmq/AidlMessageQueue.h>
 #include <powermanager/PowerHalController.h>
 #pragma clang diagnostic pop
 
@@ -59,6 +60,7 @@
     // set before onBootFinished, which gates all methods that run on threads other than SF main
     virtual bool usePowerHintSession() = 0;
     virtual bool supportsPowerHintSession() = 0;
+    virtual bool supportsGpuReporting() = 0;
 
     // Sends a power hint that updates to the target work duration for the frame
     virtual void updateTargetWorkDuration(Duration targetDuration) = 0;
@@ -68,6 +70,8 @@
     virtual void enablePowerHintSession(bool enabled) = 0;
     // Initializes the power hint session
     virtual bool startPowerHintSession(std::vector<int32_t>&& threadIds) = 0;
+    // Provides PowerAdvisor with gpu start time
+    virtual void setGpuStartTime(DisplayId displayId, TimePoint startTime) = 0;
     // Provides PowerAdvisor with a copy of the gpu fence so it can determine the gpu end time
     virtual void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) = 0;
     // Reports the start and end times of a hwc validate call this frame for a given display
@@ -80,9 +84,8 @@
     virtual void setExpectedPresentTime(TimePoint expectedPresentTime) = 0;
     // Reports the most recent present fence time and end time once known
     virtual void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) = 0;
-    // Reports whether a display used client composition this frame
-    virtual void setRequiresClientComposition(DisplayId displayId,
-                                              bool requiresClientComposition) = 0;
+    // Reports whether a display requires RenderEngine to draw
+    virtual void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine) = 0;
     // Reports whether a given display skipped validation this frame
     virtual void setSkippedValidate(DisplayId displayId, bool skipped) = 0;
     // Reports when a hwc present is delayed, and the time that it will resume
@@ -121,17 +124,19 @@
     bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
     bool usePowerHintSession() override;
     bool supportsPowerHintSession() override;
+    bool supportsGpuReporting() override;
     void updateTargetWorkDuration(Duration targetDuration) override;
     void reportActualWorkDuration() override;
     void enablePowerHintSession(bool enabled) override;
     bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+    void setGpuStartTime(DisplayId displayId, TimePoint startTime) override;
     void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) override;
     void setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
                               TimePoint validateEndTime) override;
     void setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
                              TimePoint presentEndTime) override;
     void setSkippedValidate(DisplayId displayId, bool skipped) override;
-    void setRequiresClientComposition(DisplayId displayId, bool requiresClientComposition) override;
+    void setRequiresRenderEngine(DisplayId displayId, bool requiresRenderEngine);
     void setExpectedPresentTime(TimePoint expectedPresentTime) override;
     void setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) override;
     void setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) override;
@@ -192,7 +197,7 @@
         std::optional<TimePoint> hwcValidateStartTime;
         std::optional<TimePoint> hwcValidateEndTime;
         std::optional<TimePoint> hwcPresentDelayedTime;
-        bool usedClientComposition = false;
+        bool requiresRenderEngine = false;
         bool skippedValidate = false;
         // Calculate high-level timing milestones from more granular display timing data
         DisplayTimeline calculateDisplayTimeline(TimePoint fenceTime);
@@ -224,15 +229,17 @@
     // Filter and sort the display ids by a given property
     std::vector<DisplayId> getOrderedDisplayIds(
             std::optional<TimePoint> DisplayTimingData::*sortBy);
-    // Estimates a frame's total work duration including gpu time.
-    std::optional<Duration> estimateWorkDuration();
+    // Estimates a frame's total work duration including gpu and gpu time.
+    std::optional<aidl::android::hardware::power::WorkDuration> 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);
+    // Whether to use the new "createHintSessionWithConfig" method
+    bool shouldCreateSessionWithConfig() REQUIRES(mHintSessionMutex);
 
     bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
+    void setUpFmq() REQUIRES(mHintSessionMutex);
     std::unordered_map<DisplayId, DisplayTimingData> mDisplayTimingData;
-
     // Current frame's delay
     Duration mFrameDelayDuration{0ns};
     // Last frame's post-composition duration
@@ -259,17 +266,25 @@
     std::optional<bool> mSupportsHintSession;
 
     std::mutex mHintSessionMutex;
-    std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
-            GUARDED_BY(mHintSessionMutex) = nullptr;
+    std::shared_ptr<power::PowerHintSessionWrapper> mHintSession GUARDED_BY(mHintSessionMutex) =
+            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<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
+    std::unique_ptr<::android::AidlMessageQueue<
+            aidl::android::hardware::power::ChannelMessage,
+            ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>>
+            mMsgQueue GUARDED_BY(mHintSessionMutex);
+    std::unique_ptr<::android::AidlMessageQueue<
+            int8_t, ::aidl::android::hardware::common::fmq::SynchronizedReadWrite>>
+            mFlagQueue GUARDED_BY(mHintSessionMutex);
+    android::hardware::EventFlag* mEventFlag;
+    uint32_t mFmqWriteMask;
     // 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> mHintSessionThreadIds;
     Duration mLastTargetDurationSent = kDefaultTargetDuration;
@@ -278,7 +293,14 @@
     std::promise<bool> mDelayReportActualMutexAcquisitonPromise;
     bool mTimingTestingMode = false;
 
-    // Whether we should emit ATRACE_INT data for hint sessions
+    // Hint session configuration data
+    aidl::android::hardware::power::SessionConfig mSessionConfig;
+
+    // Whether createHintSessionWithConfig is supported, assume true until it fails
+    bool mSessionConfigSupported = true;
+    bool mFirstConfigSupportCheck = true;
+
+    // Whether we should emit SFTRACE_INT data for hint sessions
     static const bool sTraceHintSessionData;
 
     // Default target duration for the hint session
@@ -295,6 +317,12 @@
     // 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};
+
+    void sendHintSessionHint(aidl::android::hardware::power::SessionHint hint);
+
+    template <aidl::android::hardware::power::ChannelMessage::ChannelMessageContents::Tag T,
+              class In>
+    bool writeHintSessionMessage(In* elements, size_t count) REQUIRES(mHintSessionMutex);
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
index 4b5a68c..384f7b2 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp
@@ -22,6 +22,7 @@
 
 #include <cinttypes>
 
+#include <com_android_graphics_libgui_flags.h>
 #include <ftl/enum.h>
 #include <ftl/flags.h>
 #include <gui/BufferItem.h>
@@ -51,7 +52,11 @@
                                              const sp<IGraphicBufferProducer>& bqProducer,
                                              const sp<IGraphicBufferConsumer>& bqConsumer,
                                              const std::string& name)
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+      : ConsumerBase(bqProducer, bqConsumer),
+#else
       : ConsumerBase(bqConsumer),
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         mHwc(hwc),
         mDisplayId(displayId),
         mDisplayName(name),
diff --git a/services/surfaceflinger/DisplayRenderArea.cpp b/services/surfaceflinger/DisplayRenderArea.cpp
index 55b395b..c63c738 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -22,22 +22,20 @@
 std::unique_ptr<RenderArea> DisplayRenderArea::create(wp<const DisplayDevice> displayWeak,
                                                       const Rect& sourceCrop, ui::Size reqSize,
                                                       ui::Dataspace reqDataSpace,
-                                                      bool hintForSeamlessTransition,
-                                                      bool allowSecureLayers) {
+                                                      ftl::Flags<Options> options) {
     if (auto display = displayWeak.promote()) {
         // Using new to access a private constructor.
-        return std::unique_ptr<DisplayRenderArea>(
-                new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace,
-                                      hintForSeamlessTransition, allowSecureLayers));
+        return std::unique_ptr<DisplayRenderArea>(new DisplayRenderArea(std::move(display),
+                                                                        sourceCrop, reqSize,
+                                                                        reqDataSpace, options));
     }
     return nullptr;
 }
 
 DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
                                      ui::Size reqSize, ui::Dataspace reqDataSpace,
-                                     bool hintForSeamlessTransition, bool allowSecureLayers)
-      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers),
+                                     ftl::Flags<Options> options)
+      : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, options),
         mDisplay(std::move(display)),
         mSourceCrop(sourceCrop) {}
 
@@ -46,7 +44,7 @@
 }
 
 bool DisplayRenderArea::isSecure() const {
-    return mAllowSecureLayers && mDisplay->isSecure();
+    return mOptions.test(Options::CAPTURE_SECURE_LAYERS) && mDisplay->isSecure();
 }
 
 sp<const DisplayDevice> DisplayRenderArea::getDisplayDevice() const {
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 4555a9e..677d019 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -29,8 +29,7 @@
 public:
     static std::unique_ptr<RenderArea> create(wp<const DisplayDevice>, const Rect& sourceCrop,
                                               ui::Size reqSize, ui::Dataspace,
-                                              bool hintForSeamlessTransition,
-                                              bool allowSecureLayers = true);
+                                              ftl::Flags<Options> options);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
@@ -39,7 +38,7 @@
 
 private:
     DisplayRenderArea(sp<const DisplayDevice>, const Rect& sourceCrop, ui::Size reqSize,
-                      ui::Dataspace, bool hintForSeamlessTransition, bool allowSecureLayers = true);
+                      ui::Dataspace, ftl::Flags<Options> options);
 
     const sp<const DisplayDevice> mDisplay;
     const Rect mSourceCrop;
diff --git a/services/surfaceflinger/Effects/Daltonizer.cpp b/services/surfaceflinger/Effects/Daltonizer.cpp
index a7090c5..65f2605 100644
--- a/services/surfaceflinger/Effects/Daltonizer.cpp
+++ b/services/surfaceflinger/Effects/Daltonizer.cpp
@@ -37,6 +37,18 @@
     }
 }
 
+void Daltonizer::setLevel(int32_t level) {
+    if (level < 0 || level > 10) {
+        return;
+    }
+
+    float newLevel = level / 10.0f;
+    if (std::fabs(mLevel - newLevel) > 0.09f) {
+        mDirty = true;
+    }
+    mLevel = newLevel;
+}
+
 const mat4& Daltonizer::operator()() {
     if (mDirty) {
         mDirty = false;
@@ -117,25 +129,24 @@
     // a color blind user and "spread" this error onto the healthy cones.
     // The matrices below perform this last step and have been chosen arbitrarily.
 
-    // The amount of correction can be adjusted here.
-
+    // Scale 0 represents no change (mColorTransform is identical matrix).
     // error spread for protanopia
-    const mat4 errp(    1.0, 0.7, 0.7, 0,
-                        0.0, 1.0, 0.0, 0,
-                        0.0, 0.0, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errp(1.0, mLevel, mLevel, 0.0,
+                    0.0,    1.0,    0.0, 0.0,
+                    0.0,    0.0,    1.0, 0.0,
+                    0.0,    0.0,    0.0, 1.0);
 
     // error spread for deuteranopia
-    const mat4 errd(    1.0, 0.0, 0.0, 0,
-                        0.7, 1.0, 0.7, 0,
-                        0.0, 0.0, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errd(   1.0, 0.0,    0.0, 0.0,
+                    mLevel, 1.0, mLevel, 0.0,
+                       0.0, 0.0,    1.0, 0.0,
+                       0.0, 0.0,    0.0, 1.0);
 
     // error spread for tritanopia
-    const mat4 errt(    1.0, 0.0, 0.0, 0,
-                        0.0, 1.0, 0.0, 0,
-                        0.7, 0.7, 1.0, 0,
-                          0,   0,   0, 1);
+    const mat4 errt(   1.0,    0.0, 0.0, 0.0,
+                       0.0,    1.0, 0.0, 0.0,
+                    mLevel, mLevel, 1.0, 0.0,
+                       0.0,    0.0, 0.0, 1.0);
 
     // And the magic happens here...
     // We construct the matrix that will perform the whole correction.
diff --git a/services/surfaceflinger/Effects/Daltonizer.h b/services/surfaceflinger/Effects/Daltonizer.h
index 2fb60e9..f5eaae7 100644
--- a/services/surfaceflinger/Effects/Daltonizer.h
+++ b/services/surfaceflinger/Effects/Daltonizer.h
@@ -21,6 +21,9 @@
 
 namespace android {
 
+// Forward declare test class
+class DaltonizerTest;
+
 enum class ColorBlindnessType {
     None,               // Disables the Daltonizer
     Protanomaly,        // L (red) cone deficient
@@ -37,10 +40,15 @@
 public:
     void setType(ColorBlindnessType type);
     void setMode(ColorBlindnessMode mode);
+    // sets level for correction saturation, [0-10].
+    void setLevel(int32_t level);
 
     // returns the color transform to apply in the shader
     const mat4& operator()();
 
+    // For testing.
+    friend class DaltonizerTest;
+
 private:
     void update();
 
@@ -48,6 +56,8 @@
     ColorBlindnessMode mMode = ColorBlindnessMode::Simulation;
     bool mDirty = true;
     mat4 mColorTransform;
+    // level of error spreading, [0.0-1.0].
+    float mLevel = 0.7f;
 };
 
 } /* namespace android */
diff --git a/services/surfaceflinger/EventLog/EventLogTags.logtags b/services/surfaceflinger/EventLog/EventLogTags.logtags
index 6c851dd..76154cb 100644
--- a/services/surfaceflinger/EventLog/EventLogTags.logtags
+++ b/services/surfaceflinger/EventLog/EventLogTags.logtags
@@ -31,7 +31,7 @@
 # 6: Percent
 # Default value for data of type int/long is 2 (bytes).
 #
-# See system/core/logcat/event.logtags for the master copy of the tags.
+# See system/logging/logcat/event.logtags for the master copy of the tags.
 
 # 60100 - 60199 reserved for surfaceflinger
 
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index d0e2d7a..47b811b 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -22,20 +22,24 @@
 
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <utils/Log.h>
-#include <utils/Trace.h>
 
 #include <chrono>
 #include <cinttypes>
 #include <numeric>
 #include <unordered_set>
 
+#include "../Jank/JankTracker.h"
+
 namespace android::frametimeline {
 
 using base::StringAppendF;
 using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
 using FrameTimelineDataSource = impl::FrameTimeline::FrameTimelineDataSource;
 
+namespace {
+
 void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
                const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
     StringAppendF(&result, "%s", indent.c_str());
@@ -317,6 +321,16 @@
     return minTime;
 }
 
+bool shouldTraceForDataSource(const FrameTimelineDataSource::TraceContext& ctx, nsecs_t timestamp) {
+    if (auto ds = ctx.GetDataSourceLocked(); ds && ds->getStartTime() > timestamp) {
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace
+
 int64_t TraceCookieCounter::getCookieForTracing() {
     return ++mTraceCookie;
 }
@@ -357,7 +371,11 @@
 
 void SurfaceFrame::setAcquireFenceTime(nsecs_t acquireFenceTime) {
     std::scoped_lock lock(mMutex);
-    mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
+    if (CC_UNLIKELY(acquireFenceTime == Fence::SIGNAL_TIME_PENDING)) {
+        mActuals.endTime = mActualQueueTime;
+    } else {
+        mActuals.endTime = std::max(acquireFenceTime, mActualQueueTime);
+    }
 }
 
 void SurfaceFrame::setDropTime(nsecs_t dropTime) {
@@ -543,12 +561,14 @@
 }
 
 void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                                      Fps displayFrameRenderRate, nsecs_t& deadlineDelta) {
+                                      Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) {
     if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
         // Cannot do any classification for invalid present time.
         mJankType = JankType::Unknown;
         mJankSeverityType = JankSeverityType::Unknown;
-        deadlineDelta = -1;
+        if (outDeadlineDelta) {
+            *outDeadlineDelta = -1;
+        }
         return;
     }
 
@@ -559,7 +579,9 @@
         mJankType = mPresentState != PresentState::Presented ? JankType::Dropped
                                                              : JankType::AppDeadlineMissed;
         mJankSeverityType = JankSeverityType::Unknown;
-        deadlineDelta = -1;
+        if (outDeadlineDelta) {
+            *outDeadlineDelta = -1;
+        }
         return;
     }
 
@@ -568,11 +590,14 @@
         return;
     }
 
-    deadlineDelta = mActuals.endTime - mPredictions.endTime;
     const nsecs_t presentDelta = mActuals.presentTime - mPredictions.presentTime;
     const nsecs_t deltaToVsync = refreshRate.getPeriodNsecs() > 0
             ? std::abs(presentDelta) % refreshRate.getPeriodNsecs()
             : 0;
+    const nsecs_t deadlineDelta = mActuals.endTime - mPredictions.endTime;
+    if (outDeadlineDelta) {
+        *outDeadlineDelta = deadlineDelta;
+    }
 
     if (deadlineDelta > mJankClassificationThresholds.deadlineThreshold) {
         mFrameReadyMetadata = FrameReadyMetadata::LateFinish;
@@ -671,25 +696,66 @@
     mActuals.presentTime = presentTime;
     nsecs_t deadlineDelta = 0;
 
-    classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, deadlineDelta);
+    classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, &deadlineDelta);
 
     if (mPredictionState != PredictionState::None) {
         // Only update janky frames if the app used vsync predictions
         mTimeStats->incrementJankyFrames({refreshRate, mRenderRate, mOwnerUid, mLayerName,
                                           mGameMode, mJankType, displayDeadlineDelta,
                                           displayPresentDelta, deadlineDelta});
+
+        gui::JankData jd;
+        jd.frameVsyncId = mToken;
+        jd.jankType = mJankType;
+        jd.frameIntervalNs =
+                (mRenderRate ? *mRenderRate : mDisplayFrameRenderRate).getPeriodNsecs();
+
+        if (mPredictionState == PredictionState::Valid) {
+            jd.scheduledAppFrameTimeNs = mPredictions.endTime - mPredictions.startTime;
+
+            // Using expected start, rather than actual, to measure the entire frame time. That is
+            // if the application starts the frame later than scheduled, include that delay in the
+            // frame time, as it usually means main thread being busy with non-rendering work.
+            if (mPresentState == PresentState::Dropped) {
+                jd.actualAppFrameTimeNs = mDropTime - mPredictions.startTime;
+            } else {
+                jd.actualAppFrameTimeNs = mActuals.endTime - mPredictions.startTime;
+            }
+        } else {
+            jd.scheduledAppFrameTimeNs = 0;
+            jd.actualAppFrameTimeNs = 0;
+        }
+
+        JankTracker::onJankData(mLayerId, jd);
     }
 }
 
-void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate) {
+    std::scoped_lock lock(mMutex);
+
+    mDisplayFrameRenderRate = displayFrameRenderRate;
+    mActuals.presentTime = mPredictions.presentTime;
+    classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, nullptr);
+}
+
+void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                                    bool filterFramesBeforeTraceStarts) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mPredictions.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedSurfaceFrameStartEvent = event->set_expected_surface_frame_start();
@@ -703,42 +769,54 @@
         expectedSurfaceFrameStartEvent->set_layer_name(mDebugName);
     });
 
-    // Expected timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        std::scoped_lock lock(mMutex);
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime + monoBootOffset));
+    if (traced) {
+        // Expected timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            std::scoped_lock lock(mMutex);
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
 
-        expectedSurfaceFrameEndEvent->set_cookie(expectedTimelineCookie);
-    });
+            expectedSurfaceFrameEndEvent->set_cookie(expectedTimelineCookie);
+        });
+    }
 }
 
-void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                                bool filterFramesBeforeTraceStarts) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = [&]() {
+            std::scoped_lock lock(mMutex);
+            // Actual start time is not yet available, so use expected start instead
+            if (mPredictionState == PredictionState::Expired) {
+                // If prediction is expired, we can't use the predicted start time. Instead, just
+                // use a start time a little earlier than the end time so that we have some info
+                // about this frame in the trace.
+                nsecs_t endTime =
+                        (mPresentState == PresentState::Dropped ? mDropTime : mActuals.endTime);
+                return endTime - kPredictionExpiredStartTimeDelta;
+            }
+
+            return mActuals.startTime == 0 ? mPredictions.startTime : mActuals.startTime;
+        }();
+
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        // Actual start time is not yet available, so use expected start instead
-        if (mPredictionState == PredictionState::Expired) {
-            // If prediction is expired, we can't use the predicted start time. Instead, just use a
-            // start time a little earlier than the end time so that we have some info about this
-            // frame in the trace.
-            nsecs_t endTime =
-                    (mPresentState == PresentState::Dropped ? mDropTime : mActuals.endTime);
-            const auto timestamp = endTime - kPredictionExpiredStartTimeDelta;
-            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
-        } else {
-            const auto timestamp =
-                    mActuals.startTime == 0 ? mPredictions.startTime : mActuals.startTime;
-            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
-        }
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualSurfaceFrameStartEvent = event->set_actual_surface_frame_start();
@@ -767,28 +845,31 @@
         actualSurfaceFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
-    // Actual timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        std::scoped_lock lock(mMutex);
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        if (mPresentState == PresentState::Dropped) {
-            packet->set_timestamp(static_cast<uint64_t>(mDropTime + monoBootOffset));
-        } else {
-            packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime + monoBootOffset));
-        }
+    if (traced) {
+        // Actual timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            std::scoped_lock lock(mMutex);
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            if (mPresentState == PresentState::Dropped) {
+                packet->set_timestamp(static_cast<uint64_t>(mDropTime + monoBootOffset));
+            } else {
+                packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime + monoBootOffset));
+            }
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* actualSurfaceFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualSurfaceFrameEndEvent = event->set_frame_end();
 
-        actualSurfaceFrameEndEvent->set_cookie(actualTimelineCookie);
-    });
+            actualSurfaceFrameEndEvent->set_cookie(actualTimelineCookie);
+        });
+    }
 }
 
 /**
  * TODO(b/178637512): add inputEventId to the perfetto trace.
  */
-void SurfaceFrame::trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::trace(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                         bool filterFramesBeforeTraceStarts) const {
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID ||
         displayFrameToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // No packets can be traced with a missing token.
@@ -797,15 +878,15 @@
     if (getPredictionState() != PredictionState::Expired) {
         // Expired predictions have zeroed timestamps. This cannot be used in any meaningful way in
         // a trace.
-        tracePredictions(displayFrameToken, monoBootOffset);
+        tracePredictions(displayFrameToken, monoBootOffset, filterFramesBeforeTraceStarts);
     }
-    traceActuals(displayFrameToken, monoBootOffset);
+    traceActuals(displayFrameToken, monoBootOffset, filterFramesBeforeTraceStarts);
 }
 
 namespace impl {
 
 int64_t TokenManager::generateTokenForPredictions(TimelineItem&& predictions) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     while (mPredictions.size() >= kMaxTokens) {
         mPredictions.erase(mPredictions.begin());
@@ -825,8 +906,12 @@
 }
 
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                             JankClassificationThresholds thresholds, bool useBootTimeClock)
+                             JankClassificationThresholds thresholds, bool useBootTimeClock,
+                             bool filterFramesBeforeTraceStarts)
       : mUseBootTimeClock(useBootTimeClock),
+        mFilterFramesBeforeTraceStarts(
+                FlagManager::getInstance().filter_frames_before_trace_starts() &&
+                filterFramesBeforeTraceStarts),
         mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)),
         mSurfaceFlingerPid(surfaceFlingerPid),
@@ -851,7 +936,7 @@
 std::shared_ptr<SurfaceFrame> FrameTimeline::createSurfaceFrameForToken(
         const FrameTimelineInfo& frameTimelineInfo, pid_t ownerPid, uid_t ownerUid, int32_t layerId,
         std::string layerName, std::string debugName, bool isBuffer, GameMode gameMode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (frameTimelineInfo.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         return std::make_shared<SurfaceFrame>(frameTimelineInfo, ownerPid, ownerUid, layerId,
                                               std::move(layerName), std::move(debugName),
@@ -887,14 +972,14 @@
 }
 
 void FrameTimeline::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
 }
 
 void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate,
                                 Fps renderRate) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->onSfWakeUp(token, refreshRate, renderRate,
                                      mTokenManager.getPredictionsForToken(token), wakeUpTime);
@@ -903,7 +988,7 @@
 void FrameTimeline::setSfPresent(nsecs_t sfPresentTime,
                                  const std::shared_ptr<FenceTime>& presentFence,
                                  const std::shared_ptr<FenceTime>& gpuFence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mMutex);
     mCurrentDisplayFrame->setActualEndTime(sfPresentTime);
     mCurrentDisplayFrame->setGpuFence(gpuFence);
@@ -912,6 +997,15 @@
     finalizeCurrentDisplayFrame();
 }
 
+void FrameTimeline::onCommitNotComposited() {
+    SFTRACE_CALL();
+    std::scoped_lock lock(mMutex);
+    mCurrentDisplayFrame->onCommitNotComposited();
+    mCurrentDisplayFrame.reset();
+    mCurrentDisplayFrame = std::make_shared<DisplayFrame>(mTimeStats, mJankClassificationThresholds,
+                                                          &mTraceCookieCounter);
+}
+
 void FrameTimeline::DisplayFrame::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame) {
     mSurfaceFrames.push_back(surfaceFrame);
 }
@@ -1094,16 +1188,29 @@
     }
 }
 
-void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
-                                                   nsecs_t monoBootOffset) const {
+void FrameTimeline::DisplayFrame::onCommitNotComposited() {
+    for (auto& surfaceFrame : mSurfaceFrames) {
+        surfaceFrame->onCommitNotComposited(mRefreshRate, mRenderRate);
+    }
+}
+
+void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                                   bool filterFramesBeforeTraceStarts) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mSurfaceFlingerPredictions.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerPredictions.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedDisplayFrameStartEvent = event->set_expected_display_frame_start();
@@ -1114,22 +1221,25 @@
         expectedDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
     });
 
-    // Expected timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime + monoBootOffset));
+    if (traced) {
+        // Expected timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(
+                    static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* expectedDisplayFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* expectedDisplayFrameEndEvent = event->set_frame_end();
 
-        expectedDisplayFrameEndEvent->set_cookie(expectedTimelineCookie);
-    });
+            expectedDisplayFrameEndEvent->set_cookie(expectedTimelineCookie);
+        });
+    }
 }
 
 void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                                  nsecs_t previousPredictionPresentTime) const {
+                                                  nsecs_t previousPredictionPresentTime,
+                                                  bool filterFramesBeforeTraceStarts) const {
     nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
     const constexpr float kThresh = 0.5f;
     const constexpr float kRange = 1.5f;
@@ -1142,10 +1252,10 @@
                     (static_cast<float>(mSurfaceFlingerPredictions.presentTime) -
                      kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
             static_cast<float>(surfaceFrame->getPredictions().presentTime) >=
-                    (static_cast<float>(previousPredictionPresentTime) -
+                    (static_cast<float>(previousPredictionPresentTime) +
                      kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
             // sf skipped frame is not considered if app is self janked
-            !surfaceFrame->isSelfJanky()) {
+            surfaceFrame->getJankType() != JankType::None && !surfaceFrame->isSelfJanky()) {
             skippedFrameStartTime = surfaceFrame->getPredictions().endTime;
             skippedFramePresentTime = surfaceFrame->getPredictions().presentTime;
             break;
@@ -1155,9 +1265,17 @@
     // add slice
     if (skippedFrameStartTime != 0 && skippedFramePresentTime != 0) {
         int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+        bool traced = false;
 
         // Actual timeline start
         FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            if (filterFramesBeforeTraceStarts &&
+                !shouldTraceForDataSource(ctx, skippedFrameStartTime)) {
+                // Do not trace packets started before tracing starts.
+                return;
+            }
+            traced = true;
+
             auto packet = ctx.NewTracePacket();
             packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
             packet->set_timestamp(static_cast<uint64_t>(skippedFrameStartTime + monoBootOffset));
@@ -1178,30 +1296,40 @@
             actualDisplayFrameStartEvent->set_jank_severity_type(toProto(JankSeverityType::None));
         });
 
-        // Actual timeline end
-        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-            auto packet = ctx.NewTracePacket();
-            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-            packet->set_timestamp(static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
+        if (traced) {
+            // Actual timeline end
+            FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+                auto packet = ctx.NewTracePacket();
+                packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+                packet->set_timestamp(
+                        static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
 
-            auto* event = packet->set_frame_timeline_event();
-            auto* actualDisplayFrameEndEvent = event->set_frame_end();
+                auto* event = packet->set_frame_timeline_event();
+                auto* actualDisplayFrameEndEvent = event->set_frame_end();
 
-            actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
-        });
+                actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+            });
+        }
     }
 }
 
-void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
-                                               nsecs_t monoBootOffset) const {
+void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                               bool filterFramesBeforeTraceStarts) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mSurfaceFlingerActuals.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerActuals.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
@@ -1220,22 +1348,25 @@
         actualDisplayFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
-    // Actual timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime + monoBootOffset));
+    if (traced) {
+        // Actual timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(
+                    static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* actualDisplayFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualDisplayFrameEndEvent = event->set_frame_end();
 
-        actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
-    });
+            actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+        });
+    }
 }
 
 nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                           nsecs_t previousPredictionPresentTime) const {
+                                           nsecs_t previousPredictionPresentTime,
+                                           bool filterFramesBeforeTraceStarts) const {
     if (mSurfaceFrames.empty()) {
         // We don't want to trace display frames without any surface frames updates as this cannot
         // be janky
@@ -1251,16 +1382,17 @@
     if (mPredictionState == PredictionState::Valid) {
         // Expired and unknown predictions have zeroed timestamps. This cannot be used in any
         // meaningful way in a trace.
-        tracePredictions(surfaceFlingerPid, monoBootOffset);
+        tracePredictions(surfaceFlingerPid, monoBootOffset, filterFramesBeforeTraceStarts);
     }
-    traceActuals(surfaceFlingerPid, monoBootOffset);
+    traceActuals(surfaceFlingerPid, monoBootOffset, filterFramesBeforeTraceStarts);
 
     for (auto& surfaceFrame : mSurfaceFrames) {
-        surfaceFrame->trace(mToken, monoBootOffset);
+        surfaceFrame->trace(mToken, monoBootOffset, filterFramesBeforeTraceStarts);
     }
 
     if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
-        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime);
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime,
+                        filterFramesBeforeTraceStarts);
     }
     return mSurfaceFlingerPredictions.presentTime;
 }
@@ -1354,8 +1486,9 @@
         const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
-        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
-                                                             mPreviousPredictionPresentTime);
+        mPreviousPredictionPresentTime =
+                displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                    mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
@@ -1371,8 +1504,9 @@
 
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
-        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
-                                                             mPreviousPredictionPresentTime);
+        mPreviousPredictionPresentTime =
+                displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                    mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
         mPreviousActualPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
@@ -1477,7 +1611,7 @@
 }
 
 void FrameTimeline::parseArgs(const Vector<String16>& args, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::unordered_map<std::string, bool> argsMap;
     for (size_t i = 0; i < args.size(); i++) {
         argsMap[std::string(String8(args[i]).c_str())] = true;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index a76f7d4..cffb61e 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -204,6 +204,8 @@
     void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
                    Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
                    nsecs_t displayPresentDelta);
+    // Sets the frame as none janky as there was no real display frame.
+    void onCommitNotComposited(Fps refreshRate, Fps displayFrameRenderRate);
     // All the timestamps are dumped relative to the baseTime
     void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
     // Dumps only the layer, token, is buffer, jank metadata, prediction and present states.
@@ -212,7 +214,8 @@
     // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
     // DisplayFrame at the trace processor side. monoBootOffset is the difference
     // between SYSTEM_TIME_BOOTTIME and SYSTEM_TIME_MONOTONIC.
-    void trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
+    void trace(int64_t displayFrameToken, nsecs_t monoBootOffset,
+               bool filterFramesBeforeTraceStarts) const;
 
     // Getter functions used only by FrameTimelineTests and SurfaceFrame internally
     TimelineItem getActuals() const;
@@ -232,10 +235,12 @@
             std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
 
 private:
-    void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
-    void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
+    void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                          bool filterFramesBeforeTraceStarts) const;
+    void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                      bool filterFramesBeforeTraceStarts) const;
     void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
-                            Fps displayFrameRenderRate, nsecs_t& deadlineDelta) REQUIRES(mMutex);
+                            Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) REQUIRES(mMutex);
 
     const int64_t mToken;
     const int32_t mInputEventId;
@@ -318,6 +323,10 @@
     virtual void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                               const std::shared_ptr<FenceTime>& gpuFence) = 0;
 
+    // Tells FrameTimeline that a frame was committed but not composited. This is used to flush
+    // all the associated surface frames.
+    virtual void onCommitNotComposited() = 0;
+
     // Args:
     // -jank : Dumps only the Display Frames that are either janky themselves
     //         or contain janky Surface Frames.
@@ -361,9 +370,15 @@
 class FrameTimeline : public android::frametimeline::FrameTimeline {
 public:
     class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
-        void OnSetup(const SetupArgs&) override{};
-        void OnStart(const StartArgs&) override{};
-        void OnStop(const StopArgs&) override{};
+    public:
+        nsecs_t getStartTime() const { return mTraceStartTime; }
+
+    private:
+        void OnSetup(const SetupArgs&) override {};
+        void OnStart(const StartArgs&) override { mTraceStartTime = systemTime(); };
+        void OnStop(const StopArgs&) override {};
+
+        nsecs_t mTraceStartTime = 0;
     };
 
     /*
@@ -384,12 +399,15 @@
         // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
         // and SYSTEM_TIME_MONOTONIC.
         nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                      nsecs_t previousPredictionPresentTime) const;
+                      nsecs_t previousPredictionPresentTime,
+                      bool filterFramesBeforeTraceStarts) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                         std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
         // Sets the appropriate metadata and classifies the jank.
         void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime);
+        // Flushes all the surface frames as those were not generating any actual display frames.
+        void onCommitNotComposited();
         // Adds the provided SurfaceFrame to the current display frame.
         void addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame);
 
@@ -416,10 +434,13 @@
 
     private:
         void dump(std::string& result, nsecs_t baseTime) const;
-        void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
-        void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+        void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                              bool filterFramesBeforeTraceStarts) const;
+        void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                          bool filterFramesBeforeTraceStarts) const;
         void addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                             nsecs_t previousActualPresentTime) const;
+                             nsecs_t previousActualPresentTime,
+                             bool filterFramesBeforeTraceStarts) const;
         void classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
                           nsecs_t previousPresentTime);
 
@@ -463,7 +484,8 @@
     };
 
     FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                  JankClassificationThresholds thresholds = {}, bool useBootTimeClock = true);
+                  JankClassificationThresholds thresholds = {}, bool useBootTimeClock = true,
+                  bool filterFramesBeforeTraceStarts = true);
     ~FrameTimeline() = default;
 
     frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
@@ -475,6 +497,7 @@
     void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
     void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
                       const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
+    void onCommitNotComposited() override;
     void parseArgs(const Vector<String16>& args, std::string& result) override;
     void setMaxDisplayFrames(uint32_t size) override;
     float computeFps(const std::unordered_set<int32_t>& layerIds) override;
@@ -507,6 +530,7 @@
     TraceCookieCounter mTraceCookieCounter;
     mutable std::mutex mMutex;
     const bool mUseBootTimeClock;
+    const bool mFilterFramesBeforeTraceStarts;
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
index 6502f36..ea51e92 100644
--- a/services/surfaceflinger/FrontEnd/DisplayInfo.h
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -21,6 +21,7 @@
 #include <gui/DisplayInfo.h>
 #include <ui/DisplayMap.h>
 #include <ui/LayerStack.h>
+#include <ui/LogicalDisplayId.h>
 #include <ui/Transform.h>
 
 namespace android::surfaceflinger::frontend {
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
index 821ac0c..d709530 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -18,6 +18,8 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
+#include <android-base/logging.h>
+
 #include "LayerHierarchy.h"
 #include "LayerLog.h"
 #include "SwapErase.h"
@@ -52,8 +54,12 @@
     mChildren = hierarchy.mChildren;
 }
 
-void LayerHierarchy::traverse(const Visitor& visitor,
-                              LayerHierarchy::TraversalPath& traversalPath) const {
+void LayerHierarchy::traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& traversalPath,
+                              uint32_t depth) const {
+    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50,
+                                    "Cycle detected in LayerHierarchy::traverse. See "
+                                    "traverse_stack_overflow_transactions.winscope");
+
     if (mLayer) {
         bool breakTraversal = !visitor(*this, traversalPath);
         if (breakTraversal) {
@@ -66,7 +72,7 @@
     for (auto& [child, childVariant] : mChildren) {
         ScopedAddToTraversalPath addChildToTraversalPath(traversalPath, child->mLayer->id,
                                                          childVariant);
-        child->traverse(visitor, traversalPath);
+        child->traverse(visitor, traversalPath, depth + 1);
     }
 }
 
@@ -153,7 +159,7 @@
         out << prefix + (isLastChild ? "└─ " : "├─ ");
         if (variant == LayerHierarchy::Variant::Relative) {
             out << "(Relative) ";
-        } else if (variant == LayerHierarchy::Variant::Mirror) {
+        } else if (LayerHierarchy::isMirror(variant)) {
             if (!includeMirroredHierarchy) {
                 out << "(Mirroring) " << *mLayer << "\n" + prefix + "   └─ ...";
                 return;
@@ -256,27 +262,36 @@
     hierarchy->mParent->updateChild(hierarchy, LayerHierarchy::Variant::Attached);
 }
 
-void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) {
-    if (root->mLayer) {
-        attachToRelativeParent(root);
-    }
-    for (auto& [child, childVariant] : root->mChildren) {
-        if (childVariant == LayerHierarchy::Variant::Detached ||
-            childVariant == LayerHierarchy::Variant::Attached) {
-            attachHierarchyToRelativeParent(child);
+std::vector<LayerHierarchy*> LayerHierarchyBuilder::getDescendants(LayerHierarchy* root) {
+    std::vector<LayerHierarchy*> hierarchies;
+    hierarchies.push_back(root);
+    std::vector<LayerHierarchy*> descendants;
+    for (size_t i = 0; i < hierarchies.size(); i++) {
+        LayerHierarchy* hierarchy = hierarchies[i];
+        if (hierarchy->mLayer) {
+            descendants.push_back(hierarchy);
         }
+        for (auto& [child, childVariant] : hierarchy->mChildren) {
+            if (childVariant == LayerHierarchy::Variant::Detached ||
+                childVariant == LayerHierarchy::Variant::Attached) {
+                hierarchies.push_back(child);
+            }
+        }
+    }
+    return descendants;
+}
+
+void LayerHierarchyBuilder::attachHierarchyToRelativeParent(LayerHierarchy* root) {
+    std::vector<LayerHierarchy*> hierarchiesToAttach = getDescendants(root);
+    for (LayerHierarchy* hierarchy : hierarchiesToAttach) {
+        attachToRelativeParent(hierarchy);
     }
 }
 
 void LayerHierarchyBuilder::detachHierarchyFromRelativeParent(LayerHierarchy* root) {
-    if (root->mLayer) {
-        detachFromRelativeParent(root);
-    }
-    for (auto& [child, childVariant] : root->mChildren) {
-        if (childVariant == LayerHierarchy::Variant::Detached ||
-            childVariant == LayerHierarchy::Variant::Attached) {
-            detachHierarchyFromRelativeParent(child);
-        }
+    std::vector<LayerHierarchy*> hierarchiesToDetach = getDescendants(root);
+    for (LayerHierarchy* hierarchy : hierarchiesToDetach) {
+        detachFromRelativeParent(hierarchy);
     }
 }
 
@@ -289,6 +304,12 @@
         LayerHierarchy* mirror = getHierarchyFromId(mirrorId);
         hierarchy->addChild(mirror, LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            LayerHierarchy* mirror = getHierarchyFromId(layer->layerIdToMirror);
+            hierarchy->addChild(mirror, LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
 void LayerHierarchyBuilder::onLayerDestroyed(RequestedLayerState* layer) {
@@ -325,7 +346,7 @@
     LayerHierarchy* hierarchy = getHierarchyFromId(layer->id);
     auto it = hierarchy->mChildren.begin();
     while (it != hierarchy->mChildren.end()) {
-        if (it->second == LayerHierarchy::Variant::Mirror) {
+        if (LayerHierarchy::isMirror(it->second)) {
             it = hierarchy->mChildren.erase(it);
         } else {
             it++;
@@ -335,6 +356,12 @@
     for (uint32_t mirrorId : layer->mirrorIds) {
         hierarchy->addChild(getHierarchyFromId(mirrorId), LayerHierarchy::Variant::Mirror);
     }
+    if (FlagManager::getInstance().detached_mirror()) {
+        if (layer->layerIdToMirror != UNASSIGNED_LAYER_ID) {
+            hierarchy->addChild(getHierarchyFromId(layer->layerIdToMirror),
+                                LayerHierarchy::Variant::Detached_Mirror);
+        }
+    }
 }
 
 void LayerHierarchyBuilder::doUpdate(
@@ -388,11 +415,11 @@
 
 void LayerHierarchyBuilder::update(LayerLifecycleManager& layerLifecycleManager) {
     if (!mInitialized) {
-        ATRACE_NAME("LayerHierarchyBuilder:init");
+        SFTRACE_NAME("LayerHierarchyBuilder:init");
         init(layerLifecycleManager.getLayers());
     } else if (layerLifecycleManager.getGlobalChanges().test(
                        RequestedLayerState::Changes::Hierarchy)) {
-        ATRACE_NAME("LayerHierarchyBuilder:update");
+        SFTRACE_NAME("LayerHierarchyBuilder:update");
         doUpdate(layerLifecycleManager.getLayers(), layerLifecycleManager.getDestroyedLayers());
     } else {
         return; // nothing to do
@@ -401,7 +428,7 @@
     uint32_t invalidRelativeRoot;
     bool hasRelZLoop = mRoot.hasRelZLoop(invalidRelativeRoot);
     while (hasRelZLoop) {
-        ATRACE_NAME("FixRelZLoop");
+        SFTRACE_NAME("FixRelZLoop");
         TransactionTraceWriter::getInstance().invoke("relz_loop_detected",
                                                      /*overwrite=*/false);
         layerLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
@@ -460,6 +487,55 @@
     return it->second;
 }
 
+void LayerHierarchyBuilder::logSampledChildren(const LayerHierarchy& hierarchy) const {
+    LOG(ERROR) << "Dumping random sampling of child layers.";
+    int sampleSize = static_cast<int>(hierarchy.mChildren.size() / 100 + 1);
+    for (const auto& [child, variant] : hierarchy.mChildren) {
+        if (rand() % sampleSize == 0) {
+            LOG(ERROR) << "Child Layer: " << *(child->mLayer);
+        }
+    }
+}
+
+void LayerHierarchyBuilder::dumpLayerSample(const LayerHierarchy& root) const {
+    LOG(ERROR) << "Dumping layer keeping > 20 children alive:";
+    // If mLayer is nullptr, it will be skipped while traversing.
+    if (!root.mLayer && root.mChildren.size() > 20) {
+        LOG(ERROR) << "ROOT has " << root.mChildren.size() << " children";
+        logSampledChildren(root);
+    }
+    root.traverse([&](const LayerHierarchy& hierarchy, const auto&) -> bool {
+        if (hierarchy.mChildren.size() <= 20) {
+            return true;
+        }
+        // mLayer is ensured to be non-null. See LayerHierarchy::traverse.
+        const auto* layer = hierarchy.mLayer;
+        const auto childrenCount = hierarchy.mChildren.size();
+        LOG(ERROR) << "Layer " << *layer << " has " << childrenCount << " children";
+
+        const auto* parent = hierarchy.mParent;
+        while (parent != nullptr) {
+            if (!parent->mLayer) break;
+            LOG(ERROR) << "Parent Layer: " << *(parent->mLayer);
+            parent = parent->mParent;
+        }
+
+        logSampledChildren(hierarchy);
+        // Stop traversing.
+        return false;
+    });
+    LOG(ERROR) << "Dumping random sampled layers.";
+    size_t numLayers = 0;
+    root.traverse([&](const LayerHierarchy& hierarchy, const auto&) -> bool {
+        if (hierarchy.mLayer) numLayers++;
+        if ((rand() % 20 == 13) && hierarchy.mLayer) {
+            LOG(ERROR) << "Layer: " << *(hierarchy.mLayer);
+        }
+        return true;
+    });
+    LOG(ERROR) << "Total layer count: " << numLayers;
+}
+
 const LayerHierarchy::TraversalPath LayerHierarchy::TraversalPath::ROOT =
         {.id = UNASSIGNED_LAYER_ID, .variant = LayerHierarchy::Attached};
 
@@ -501,7 +577,7 @@
     // stored to reset the id upon destruction.
     traversalPath.id = layerId;
     traversalPath.variant = variant;
-    if (variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(variant)) {
         traversalPath.mirrorRootIds.emplace_back(mParentPath.id);
     } else if (variant == LayerHierarchy::Variant::Relative) {
         if (std::find(traversalPath.relativeRootIds.begin(), traversalPath.relativeRootIds.end(),
@@ -516,7 +592,7 @@
 LayerHierarchy::ScopedAddToTraversalPath::~ScopedAddToTraversalPath() {
     // Reset the traversal id to its original parent state using the state that was saved in
     // the constructor.
-    if (mTraversalPath.variant == LayerHierarchy::Variant::Mirror) {
+    if (LayerHierarchy::isMirror(mTraversalPath.variant)) {
         mTraversalPath.mirrorRootIds.pop_back();
     } else if (mTraversalPath.variant == LayerHierarchy::Variant::Relative) {
         mTraversalPath.relativeRootIds.pop_back();
diff --git a/services/surfaceflinger/FrontEnd/LayerHierarchy.h b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
index a1c73c3..47d0041 100644
--- a/services/surfaceflinger/FrontEnd/LayerHierarchy.h
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -35,6 +35,7 @@
 // Detached - child of the parent but currently relative parented to another layer
 // Relative - relative child of the parent
 // Mirror - mirrored from another layer
+// Detached_Mirror - mirrored from another layer, ignoring local transform
 //
 // By representing the hierarchy as a graph, we can represent mirrored layer hierarchies without
 // cloning the layer requested state. The mirrored hierarchy and its corresponding
@@ -43,13 +44,18 @@
 class LayerHierarchy {
 public:
     enum Variant : uint32_t {
-        Attached, // child of the parent
-        Detached, // child of the parent but currently relative parented to another layer
-        Relative, // relative child of the parent
-        Mirror,   // mirrored from another layer
+        Attached,        // child of the parent
+        Detached,        // child of the parent but currently relative parented to another layer
+        Relative,        // relative child of the parent
+        Mirror,          // mirrored from another layer
+        Detached_Mirror, // mirrored from another layer, ignoring local transform
         ftl_first = Attached,
-        ftl_last = Mirror,
+        ftl_last = Detached_Mirror,
     };
+    static inline bool isMirror(Variant variant) {
+        return ((variant == Mirror) || (variant == Detached_Mirror));
+    }
+
     // Represents a unique path to a node.
     // The layer hierarchy is represented as a graph. Each node can be visited by multiple parents.
     // This allows us to represent mirroring in an efficient way. See the example below:
@@ -141,7 +147,7 @@
         if (mLayer) {
             root.id = mLayer->id;
         }
-        traverse(visitor, root);
+        traverse(visitor, root, /*depth=*/0);
     }
 
     // Traverse the hierarchy in z-order, skipping children that have relative parents.
@@ -184,7 +190,8 @@
     void sortChildrenByZOrder();
     void updateChild(LayerHierarchy*, LayerHierarchy::Variant);
     void traverseInZOrder(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
-    void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent) const;
+    void traverse(const Visitor& visitor, LayerHierarchy::TraversalPath& parent,
+                  uint32_t depth = 0) const;
     void dump(std::ostream& out, const std::string& prefix, LayerHierarchy::Variant variant,
               bool isLastChild, bool includeMirroredHierarchy) const;
 
@@ -204,13 +211,17 @@
     const LayerHierarchy& getHierarchy() const;
     const LayerHierarchy& getOffscreenHierarchy() const;
     std::string getDebugString(uint32_t layerId, uint32_t depth = 0) const;
+    void dumpLayerSample(const LayerHierarchy& layerHierarchy) const;
 
 private:
+    void logSampledChildren(const LayerHierarchy& hierarchy) const;
+
     void onLayerAdded(RequestedLayerState* layer);
     void attachToParent(LayerHierarchy*);
     void detachFromParent(LayerHierarchy*);
     void attachToRelativeParent(LayerHierarchy*);
     void detachFromRelativeParent(LayerHierarchy*);
+    std::vector<LayerHierarchy*> getDescendants(LayerHierarchy*);
     void attachHierarchyToRelativeParent(LayerHierarchy*);
     void detachHierarchyFromRelativeParent(LayerHierarchy*);
     void init(const std::vector<std::unique_ptr<RequestedLayerState>>&);
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
index e864ff6..f1091a6 100644
--- a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -73,8 +73,10 @@
             // Check if we are mirroring a single layer, and if so add it to the list of children
             // to be mirrored.
             layer.layerIdToMirror = linkLayer(layer.layerIdToMirror, layer.id);
-            if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
-                layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+            if (!FlagManager::getInstance().detached_mirror()) {
+                if (layer.layerIdToMirror != UNASSIGNED_LAYER_ID) {
+                    layer.mirrorIds.emplace_back(layer.layerIdToMirror);
+                }
             }
         }
         layer.touchCropId = linkLayer(layer.touchCropId, layer.id);
@@ -151,6 +153,10 @@
             if (swapErase(linkedLayer->mirrorIds, layer.id)) {
                 linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
             }
+            if (linkedLayer->layerIdToMirror == layer.id) {
+                linkedLayer->layerIdToMirror = UNASSIGNED_LAYER_ID;
+                linkedLayer->changes |= RequestedLayerState::Changes::Mirror;
+            }
             if (linkedLayer->touchCropId == layer.id) {
                 linkedLayer->touchCropId = UNASSIGNED_LAYER_ID;
             }
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
index ea06cf6..e5f6b7b 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -127,9 +127,8 @@
     pid = state.ownerPid;
     changes = RequestedLayerState::Changes::Created;
     clientChanges = 0;
-    mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror
-            ? path
-            : LayerHierarchy::TraversalPath::ROOT;
+    mirrorRootPath =
+            LayerHierarchy::isMirror(path.variant) ? path : LayerHierarchy::TraversalPath::ROOT;
     reachablilty = LayerSnapshot::Reachablilty::Unreachable;
     frameRateSelectionPriority = state.frameRateSelectionPriority;
     layerMetadata = state.metadata;
@@ -323,6 +322,10 @@
             << touchableRegion.bottom << "," << touchableRegion.right << "}"
             << "}";
     }
+
+    if (obj.edgeExtensionEffect.hasEffect()) {
+        out << obj.edgeExtensionEffect;
+    }
     return out;
 }
 
@@ -472,13 +475,14 @@
         geomContentCrop = requested.getBufferCrop();
     }
 
-    if (forceUpdate ||
-        requested.what &
-                (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
-                 layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
-                 layer_state_t::eBufferTransformChanged |
-                 layer_state_t::eTransformToDisplayInverseChanged) ||
-        requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) {
+    if ((forceUpdate ||
+         requested.what &
+                 (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
+                  layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
+                  layer_state_t::eBufferTransformChanged |
+                  layer_state_t::eTransformToDisplayInverseChanged) ||
+         requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) &&
+        !ignoreLocalTransform) {
         localTransform = requested.getTransform(displayRotationFlags);
         localTransformInverse = localTransform.inverse();
     }
@@ -492,8 +496,10 @@
         requested.what &
                 (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
                  layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged |
-                 layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged)) {
-        forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect();
+                 layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged |
+                 layer_state_t::eEdgeExtensionChanged)) {
+        forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect() ||
+                edgeExtensionEffect.hasEffect();
     }
 
     if (forceUpdate ||
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 73ee22f..398e64a 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -80,8 +80,11 @@
     ui::Transform localTransformInverse;
     gui::WindowInfo inputInfo;
     ui::Transform localTransform;
+    // set to true if this snapshot will ignore local transforms. Used when the snapshot
+    // is a mirror root
+    bool ignoreLocalTransform;
     gui::DropInputMode dropInputMode;
-    bool isTrustedOverlay;
+    gui::TrustedOverlay trustedOverlay;
     gui::GameMode gameMode;
     scheduler::LayerInfo::FrameRate frameRate;
     scheduler::LayerInfo::FrameRate inheritedFrameRate;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index 0966fe0..ee605b7 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -22,8 +22,9 @@
 #include <numeric>
 #include <optional>
 
+#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <ftl/small_map.h>
-#include <gui/TraceUtils.h>
 #include <ui/DisplayMap.h>
 #include <ui/FloatRect.h>
 
@@ -255,6 +256,9 @@
 }
 
 void updateVisibility(LayerSnapshot& snapshot, bool visible) {
+    if (snapshot.isVisible != visible) {
+        snapshot.changes |= RequestedLayerState::Changes::Visibility;
+    }
     snapshot.isVisible = visible;
 
     // TODO(b/238781169) we are ignoring this compat for now, since we will have
@@ -275,24 +279,6 @@
           snapshot.getDebugString().c_str());
 }
 
-bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
-    if (requested.potentialCursor) {
-        return false;
-    }
-
-    if (snapshot.inputInfo.token != nullptr) {
-        return true;
-    }
-
-    if (snapshot.hasBufferOrSidebandStream()) {
-        return true;
-    }
-
-    return requested.windowInfoHandle &&
-            requested.windowInfoHandle->getInfo()->inputConfig.test(
-                    gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
-}
-
 void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requested,
                     const LayerSnapshotBuilder::Args& args) {
     snapshot.metadata.clear();
@@ -313,6 +299,18 @@
     }
 }
 
+void updateMetadataAndGameMode(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+                               const LayerSnapshotBuilder::Args& args,
+                               const LayerSnapshot& parentSnapshot) {
+    snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE) ? requested.gameMode
+                                                                        : parentSnapshot.gameMode;
+    updateMetadata(snapshot, requested, args);
+    if (args.includeMetadata) {
+        snapshot.layerMetadata = parentSnapshot.layerMetadata;
+        snapshot.layerMetadata.merge(requested.metadata);
+    }
+}
+
 void clearChanges(LayerSnapshot& snapshot) {
     snapshot.changes.clear();
     snapshot.clientChanges = 0;
@@ -348,6 +346,7 @@
     snapshot.geomLayerBounds = getMaxDisplayBounds({});
     snapshot.roundedCorner = RoundedCornerState();
     snapshot.stretchEffect = {};
+    snapshot.edgeExtensionEffect = {};
     snapshot.outputFilter.layerStack = ui::DEFAULT_LAYER_STACK;
     snapshot.outputFilter.toInternalDisplay = false;
     snapshot.isSecure = false;
@@ -358,10 +357,11 @@
     snapshot.relativeLayerMetadata.mMap.clear();
     snapshot.inputInfo.touchOcclusionMode = gui::TouchOcclusionMode::BLOCK_UNTRUSTED;
     snapshot.dropInputMode = gui::DropInputMode::NONE;
-    snapshot.isTrustedOverlay = false;
+    snapshot.trustedOverlay = gui::TrustedOverlay::UNSET;
     snapshot.gameMode = gui::GameMode::Unsupported;
     snapshot.frameRate = {};
     snapshot.fixedTransformHint = ui::Transform::ROT_INVALID;
+    snapshot.ignoreLocalTransform = false;
     return snapshot;
 }
 
@@ -382,7 +382,7 @@
 
     // There are only content changes which do not require any child layer snapshots to be updated.
     ALOGV("%s", __func__);
-    ATRACE_NAME("FastPath");
+    SFTRACE_NAME("FastPath");
 
     uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
     if (forceUpdate || args.displayChanges) {
@@ -416,7 +416,7 @@
 }
 
 void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
-    ATRACE_NAME("UpdateSnapshots");
+    SFTRACE_NAME("UpdateSnapshots");
     LayerSnapshot rootSnapshot = args.rootSnapshot;
     if (args.parentCrop) {
         rootSnapshot.geomLayerBounds = *args.parentCrop;
@@ -543,6 +543,7 @@
         updateSnapshot(*snapshot, args, *layer, parentSnapshot, traversalPath);
     }
 
+    bool childHasValidFrameRate = false;
     for (auto& [childHierarchy, variant] : hierarchy.mChildren) {
         LayerHierarchy::ScopedAddToTraversalPath addChildToPath(traversalPath,
                                                                 childHierarchy->getLayer()->id,
@@ -550,7 +551,8 @@
         const LayerSnapshot& childSnapshot =
                 updateSnapshotsInHierarchy(args, *childHierarchy, traversalPath, *snapshot,
                                            depth + 1);
-        updateFrameRateFromChildSnapshot(*snapshot, childSnapshot, args);
+        updateFrameRateFromChildSnapshot(*snapshot, childSnapshot, *childHierarchy->getLayer(),
+                                         args, &childHasValidFrameRate);
     }
 
     return *snapshot;
@@ -575,9 +577,11 @@
     mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path));
     LayerSnapshot* snapshot = mSnapshots.back().get();
     snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
-    if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) {
+    if (path.isClone() && !LayerHierarchy::isMirror(path.variant)) {
         snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath;
     }
+    snapshot->ignoreLocalTransform =
+            path.isClone() && path.variant == LayerHierarchy::Variant::Detached_Mirror;
     mPathToSnapshot[path] = snapshot;
 
     mIdToSnapshots.emplace(path.id, snapshot);
@@ -655,9 +659,10 @@
     }
 }
 
-void LayerSnapshotBuilder::updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
-                                                            const LayerSnapshot& childSnapshot,
-                                                            const Args& args) {
+void LayerSnapshotBuilder::updateFrameRateFromChildSnapshot(
+        LayerSnapshot& snapshot, const LayerSnapshot& childSnapshot,
+        const RequestedLayerState& /* requestedChildState */, const Args& args,
+        bool* outChildHasValidFrameRate) {
     if (args.forceUpdate == ForceUpdateFlags::NONE &&
         !args.layerLifecycleManager.getGlobalChanges().any(
                 RequestedLayerState::Changes::Hierarchy) &&
@@ -667,7 +672,7 @@
     }
 
     using FrameRateCompatibility = scheduler::FrameRateCompatibility;
-    if (snapshot.frameRate.isValid()) {
+    if (snapshot.inheritedFrameRate.isValid() || *outChildHasValidFrameRate) {
         // we already have a valid framerate.
         return;
     }
@@ -684,13 +689,18 @@
     const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
             childSnapshot.frameRate.vote.type == FrameRateCompatibility::Exact;
 
-    bool childHasValidFrameRate = layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
+    *outChildHasValidFrameRate |= layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
             layerVotedWithCategory || layerVotedWithExactCompatibility;
 
     // If we don't have a valid frame rate, but the children do, we set this
     // layer as NoVote to allow the children to control the refresh rate
-    if (childHasValidFrameRate) {
-        snapshot.frameRate = scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
+    static const auto noVote =
+            scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
+    if (*outChildHasValidFrameRate) {
+        snapshot.frameRate = noVote;
+        snapshot.changes |= RequestedLayerState::Changes::FrameRate;
+    } else if (snapshot.frameRate != snapshot.inheritedFrameRate) {
+        snapshot.frameRate = snapshot.inheritedFrameRate;
         snapshot.changes |= RequestedLayerState::Changes::FrameRate;
     }
 }
@@ -732,7 +742,19 @@
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
-        snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+        switch (requested.trustedOverlay) {
+            case gui::TrustedOverlay::UNSET:
+                snapshot.trustedOverlay = parentSnapshot.trustedOverlay;
+                break;
+            case gui::TrustedOverlay::DISABLED:
+                snapshot.trustedOverlay = FlagManager::getInstance().override_trusted_overlay()
+                        ? requested.trustedOverlay
+                        : parentSnapshot.trustedOverlay;
+                break;
+            case gui::TrustedOverlay::ENABLED:
+                snapshot.trustedOverlay = requested.trustedOverlay;
+                break;
+        }
     }
 
     if (snapshot.isHiddenByPolicyFromParent &&
@@ -743,6 +765,12 @@
                                  RequestedLayerState::Changes::Input)) {
             updateInput(snapshot, requested, parentSnapshot, path, args);
         }
+        if (forceUpdate ||
+            (args.includeMetadata &&
+             snapshot.changes.any(RequestedLayerState::Changes::Metadata |
+                                  RequestedLayerState::Changes::Geometry))) {
+            updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
+        }
         return;
     }
 
@@ -772,6 +800,32 @@
                 : parentSnapshot.stretchEffect;
     }
 
+    if (forceUpdate ||
+        (snapshot.clientChanges | parentSnapshot.clientChanges) &
+                layer_state_t::eEdgeExtensionChanged) {
+        if (requested.edgeExtensionParameters.extendLeft ||
+            requested.edgeExtensionParameters.extendRight ||
+            requested.edgeExtensionParameters.extendTop ||
+            requested.edgeExtensionParameters.extendBottom) {
+            // This is the root layer to which the extension is applied
+            snapshot.edgeExtensionEffect =
+                    EdgeExtensionEffect(requested.edgeExtensionParameters.extendLeft,
+                                        requested.edgeExtensionParameters.extendRight,
+                                        requested.edgeExtensionParameters.extendTop,
+                                        requested.edgeExtensionParameters.extendBottom);
+        } else if (parentSnapshot.clientChanges & layer_state_t::eEdgeExtensionChanged) {
+            // Extension is inherited
+            snapshot.edgeExtensionEffect = parentSnapshot.edgeExtensionEffect;
+        } else {
+            // There is no edge extension
+            snapshot.edgeExtensionEffect.reset();
+        }
+        if (snapshot.edgeExtensionEffect.hasEffect()) {
+            snapshot.clientChanges |= layer_state_t::eEdgeExtensionChanged;
+            snapshot.changes |= RequestedLayerState::Changes::Geometry;
+        }
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eColorTransformChanged) {
         if (!parentSnapshot.colorTransformIsIdentity) {
             snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
@@ -782,15 +836,10 @@
         }
     }
 
-    if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
-        snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
-                ? requested.gameMode
-                : parentSnapshot.gameMode;
-        updateMetadata(snapshot, requested, args);
-        if (args.includeMetadata) {
-            snapshot.layerMetadata = parentSnapshot.layerMetadata;
-            snapshot.layerMetadata.merge(requested.metadata);
-        }
+    if (forceUpdate ||
+        snapshot.changes.any(RequestedLayerState::Changes::Metadata |
+                             RequestedLayerState::Changes::Hierarchy)) {
+        updateMetadataAndGameMode(snapshot, requested, args, parentSnapshot);
     }
 
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
@@ -867,6 +916,10 @@
         updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags);
     }
 
+    if (snapshot.edgeExtensionEffect.hasEffect()) {
+        updateBoundsForEdgeExtension(snapshot);
+    }
+
     if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
         snapshot.changes.any(RequestedLayerState::Changes::Geometry |
                              RequestedLayerState::Changes::BufferUsageFlags)) {
@@ -885,8 +938,8 @@
     }
 
     // computed snapshot properties
-    snapshot.forceClientComposition =
-            snapshot.shadowSettings.length > 0 || snapshot.stretchEffect.hasEffect();
+    snapshot.forceClientComposition = snapshot.shadowSettings.length > 0 ||
+            snapshot.stretchEffect.hasEffect() || snapshot.edgeExtensionEffect.hasEffect();
     snapshot.contentOpaque = snapshot.isContentOpaque();
     snapshot.isOpaque = snapshot.contentOpaque && !snapshot.roundedCorner.hasRoundedCorners() &&
             snapshot.color.a == 1.f;
@@ -941,6 +994,31 @@
     }
 }
 
+/**
+ * According to the edges that we are requested to extend, we increase the bounds to the maximum
+ * extension allowed by the crop (parent crop + requested crop). The animation that called
+ * Transition#setEdgeExtensionEffect is in charge of setting the requested crop.
+ * @param snapshot
+ */
+void LayerSnapshotBuilder::updateBoundsForEdgeExtension(LayerSnapshot& snapshot) {
+    EdgeExtensionEffect& effect = snapshot.edgeExtensionEffect;
+
+    if (effect.extendsEdge(LEFT)) {
+        snapshot.geomLayerBounds.left = snapshot.geomLayerCrop.left;
+    }
+    if (effect.extendsEdge(RIGHT)) {
+        snapshot.geomLayerBounds.right = snapshot.geomLayerCrop.right;
+    }
+    if (effect.extendsEdge(TOP)) {
+        snapshot.geomLayerBounds.top = snapshot.geomLayerCrop.top;
+    }
+    if (effect.extendsEdge(BOTTOM)) {
+        snapshot.geomLayerBounds.bottom = snapshot.geomLayerCrop.bottom;
+    }
+
+    snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
+}
+
 void LayerSnapshotBuilder::updateLayerBounds(LayerSnapshot& snapshot,
                                              const RequestedLayerState& requested,
                                              const LayerSnapshot& parentSnapshot,
@@ -980,11 +1058,12 @@
     FloatRect parentBounds = parentSnapshot.geomLayerBounds;
     parentBounds = snapshot.localTransform.inverse().transform(parentBounds);
     snapshot.geomLayerBounds =
-            (requested.externalTexture) ? snapshot.bufferSize.toFloatRect() : parentBounds;
+            requested.externalTexture ? snapshot.bufferSize.toFloatRect() : parentBounds;
+    snapshot.geomLayerCrop = parentBounds;
     if (!requested.crop.isEmpty()) {
-        snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(requested.crop.toFloatRect());
+        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop.toFloatRect());
     }
-    snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(parentBounds);
+    snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(snapshot.geomLayerCrop);
     snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
     const Rect geomLayerBoundsWithoutTransparentRegion =
             RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
@@ -1028,6 +1107,8 @@
                                        const LayerSnapshot& parentSnapshot,
                                        const LayerHierarchy::TraversalPath& path,
                                        const Args& args) {
+    using InputConfig = gui::WindowInfo::InputConfig;
+
     if (requested.windowInfoHandle) {
         snapshot.inputInfo = *requested.windowInfoHandle->getInfo();
     } else {
@@ -1040,7 +1121,8 @@
     snapshot.touchCropId = requested.touchCropId;
 
     snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
-    snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+    snapshot.inputInfo.displayId =
+            ui::LogicalDisplayId{static_cast<int32_t>(snapshot.outputFilter.layerStack.id)};
     snapshot.inputInfo.touchOcclusionMode = requested.hasInputInfo()
             ? requested.windowInfoHandle->getInfo()->touchOcclusionMode
             : parentSnapshot.inputInfo.touchOcclusionMode;
@@ -1056,8 +1138,13 @@
         snapshot.dropInputMode = gui::DropInputMode::NONE;
     }
 
+    if (snapshot.isSecure ||
+        parentSnapshot.inputInfo.inputConfig.test(InputConfig::SENSITIVE_FOR_PRIVACY)) {
+        snapshot.inputInfo.inputConfig |= InputConfig::SENSITIVE_FOR_PRIVACY;
+    }
+
     updateVisibility(snapshot, snapshot.isVisible);
-    if (!needsInputInfo(snapshot, requested)) {
+    if (!requested.needsInputInfo()) {
         return;
     }
 
@@ -1067,15 +1154,15 @@
     bool noValidDisplay = !displayInfoOpt.has_value();
     auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
 
-    if (!requested.windowInfoHandle) {
-        snapshot.inputInfo.inputConfig = gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL;
+    if (!requested.hasInputInfo()) {
+        snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
     }
     fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
 
     if (noValidDisplay) {
         // Do not let the window receive touches if it is not associated with a valid display
         // transform. We still allow the window to receive keys and prevent ANRs.
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::NOT_TOUCHABLE;
+        snapshot.inputInfo.inputConfig |= InputConfig::NOT_TOUCHABLE;
     }
 
     snapshot.inputInfo.alpha = snapshot.color.a;
@@ -1085,7 +1172,7 @@
     // If the window will be blacked out on a display because the display does not have the secure
     // flag and the layer has the secure flag set, then drop input.
     if (!displayInfo.isSecure && snapshot.isSecure) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT;
+        snapshot.inputInfo.inputConfig |= InputConfig::DROP_INPUT;
     }
 
     if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) {
@@ -1101,8 +1188,8 @@
 
     // Inherit the trusted state from the parent hierarchy, but don't clobber the trusted state
     // if it was set by WM for a known system overlay
-    if (snapshot.isTrustedOverlay) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::TRUSTED_OVERLAY;
+    if (snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED) {
+        snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
     }
 
     snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
@@ -1110,10 +1197,10 @@
     // 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 (path.isClone()) {
-        snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::CLONE;
+        snapshot.inputInfo.inputConfig |= InputConfig::CLONE;
         // 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);
+        snapshot.inputInfo.inputConfig.clear(InputConfig::WATCH_OUTSIDE_TOUCH);
     }
 }
 
@@ -1151,6 +1238,21 @@
     }
 }
 
+void LayerSnapshotBuilder::forEachSnapshot(const Visitor& visitor,
+                                           const ConstPredicate& predicate) {
+    for (int i = 0; i < mNumInterestingSnapshots; i++) {
+        std::unique_ptr<LayerSnapshot>& snapshot = mSnapshots.at((size_t)i);
+        if (!predicate(*snapshot)) continue;
+        visitor(snapshot);
+    }
+}
+
+void LayerSnapshotBuilder::forEachSnapshot(const ConstVisitor& visitor) const {
+    for (auto& snapshot : mSnapshots) {
+        visitor(*snapshot);
+    }
+}
+
 void LayerSnapshotBuilder::forEachInputSnapshot(const ConstVisitor& visitor) const {
     for (int i = mNumInterestingSnapshots - 1; i >= 0; i--) {
         LayerSnapshot& snapshot = *mSnapshots[(size_t)i];
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
index 1cec018..486cb33 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h
@@ -86,6 +86,14 @@
     // Visit each visible snapshot in z-order and move the snapshot if needed
     void forEachVisibleSnapshot(const Visitor& visitor);
 
+    typedef std::function<bool(const LayerSnapshot& snapshot)> ConstPredicate;
+    // Visit each snapshot that satisfies the predicate and move the snapshot if needed with visible
+    // snapshots in z-order
+    void forEachSnapshot(const Visitor& visitor, const ConstPredicate& predicate);
+
+    // Visit each snapshot
+    void forEachSnapshot(const ConstVisitor& visitor) const;
+
     // Visit each snapshot interesting to input reverse z-order
     void forEachInputSnapshot(const ConstVisitor& visitor) const;
 
@@ -108,6 +116,10 @@
     static void resetRelativeState(LayerSnapshot& snapshot);
     static void updateRoundedCorner(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
                                     const LayerSnapshot& parentSnapshot, const Args& args);
+    static bool extensionEdgeSharedWithParent(LayerSnapshot& snapshot,
+                                              const RequestedLayerState& requested,
+                                              const LayerSnapshot& parentSnapshot);
+    static void updateBoundsForEdgeExtension(LayerSnapshot& snapshot);
     void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
                            const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags);
     static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
@@ -121,7 +133,9 @@
                                   const RequestedLayerState& layer,
                                   const LayerSnapshot& parentSnapshot);
     void updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
-                                          const LayerSnapshot& childSnapshot, const Args& args);
+                                          const LayerSnapshot& childSnapshot,
+                                          const RequestedLayerState& requestedCHildState,
+                                          const Args& args, bool* outChildHasValidFrameRate);
     void updateTouchableRegionCrop(const Args& args);
 
     std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*,
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index cb0e2a1..5734ccf 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -19,7 +19,7 @@
 #undef LOG_TAG
 #define LOG_TAG "SurfaceFlinger"
 
-#include <gui/TraceUtils.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
 #include <sys/types.h>
@@ -62,6 +62,8 @@
     metadata.merge(args.metadata);
     changes |= RequestedLayerState::Changes::Metadata;
     handleAlive = true;
+    // TODO: b/305254099 remove once we don't pass invisible windows to input
+    windowInfoHandle = nullptr;
     if (parentId != UNASSIGNED_LAYER_ID) {
         canBeRoot = false;
     }
@@ -118,7 +120,7 @@
     shadowRadius = 0.f;
     fixedTransformHint = ui::Transform::ROT_INVALID;
     destinationFrame.makeInvalid();
-    isTrustedOverlay = false;
+    trustedOverlay = gui::TrustedOverlay::UNSET;
     dropInputMode = gui::DropInputMode::NONE;
     dimmingEnabled = true;
     defaultFrameRateCompatibility = static_cast<int8_t>(scheduler::FrameRateCompatibility::Default);
@@ -163,7 +165,9 @@
     LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
 
     if (clientState.what & layer_state_t::eFlagsChanged) {
-        if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
+        if ((oldFlags ^ flags) &
+            (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque |
+             layer_state_t::eLayerSecure)) {
             changes |= RequestedLayerState::Changes::Visibility |
                     RequestedLayerState::Changes::VisibleRegion;
         }
@@ -326,6 +330,7 @@
                 changes |= RequestedLayerState::Changes::GameMode;
             }
         }
+        changes |= RequestedLayerState::Changes::Metadata;
     }
     if (clientState.what & layer_state_t::eFrameRateChanged) {
         const auto compatibility =
@@ -550,6 +555,24 @@
             windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
 }
 
+bool RequestedLayerState::needsInputInfo() const {
+    if (potentialCursor) {
+        return false;
+    }
+
+    if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+        return true;
+    }
+
+    if (!windowInfoHandle) {
+        return false;
+    }
+
+    const auto windowInfo = windowInfoHandle->getInfo();
+    return windowInfo->token != nullptr ||
+            windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
+}
+
 bool RequestedLayerState::hasBlur() const {
     return backgroundBlurRadius > 0 || blurRegions.size() > 0;
 }
@@ -578,39 +601,44 @@
 bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const {
     static constexpr uint64_t requiredFlags = layer_state_t::eBufferChanged;
     if ((s.what & requiredFlags) != requiredFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
-                              (s.what | requiredFlags) & ~s.what);
+        SFTRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+                               (s.what | requiredFlags) & ~s.what);
         return false;
     }
 
-    static constexpr uint64_t deniedFlags = layer_state_t::eProducerDisconnect |
-            layer_state_t::eLayerChanged | layer_state_t::eRelativeLayerChanged |
-            layer_state_t::eTransparentRegionChanged | layer_state_t::eFlagsChanged |
+    const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
+            layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
             layer_state_t::eBlurRegionsChanged | layer_state_t::eLayerStackChanged |
-            layer_state_t::eAutoRefreshChanged | layer_state_t::eReparent;
+            layer_state_t::eReparent |
+            (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+                     ? 0
+                     : (layer_state_t::eAutoRefreshChanged | layer_state_t::eFlagsChanged));
     if (s.what & deniedFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
-                              s.what & deniedFlags);
+        SFTRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+                               s.what & deniedFlags);
         return false;
     }
 
-    bool changedFlags = diff(s);
-    static constexpr auto deniedChanges = layer_state_t::ePositionChanged |
-            layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged |
-            layer_state_t::eBackgroundColorChanged | layer_state_t::eMatrixChanged |
-            layer_state_t::eCornerRadiusChanged | layer_state_t::eBackgroundBlurRadiusChanged |
-            layer_state_t::eBufferTransformChanged |
+    const uint64_t changedFlags = diff(s);
+    const uint64_t deniedChanges = layer_state_t::ePositionChanged | layer_state_t::eAlphaChanged |
+            layer_state_t::eColorTransformChanged | layer_state_t::eBackgroundColorChanged |
+            layer_state_t::eMatrixChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBufferTransformChanged |
             layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
             layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
             layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged |
             layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged |
             layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged |
-            layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged |
-            layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged |
-            layer_state_t::eDesiredHdrHeadroomChanged;
+            layer_state_t::eEdgeExtensionChanged | layer_state_t::eBufferCropChanged |
+            layer_state_t::eDestinationFrameChanged | layer_state_t::eDimmingEnabledChanged |
+            layer_state_t::eExtendedRangeBrightnessChanged |
+            layer_state_t::eDesiredHdrHeadroomChanged |
+            (FlagManager::getInstance().latch_unsignaled_with_auto_refresh_changed()
+                     ? layer_state_t::eFlagsChanged
+                     : 0);
     if (changedFlags & deniedChanges) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
-                              s.what & deniedChanges);
+        SFTRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
+                               changedFlags & deniedChanges);
         return false;
     }
 
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 09f33de..1d96dff 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -26,6 +26,7 @@
 #include "TransactionState.h"
 
 namespace android::surfaceflinger::frontend {
+using namespace ftl::flag_operators;
 
 // Stores client requested states for a layer.
 // This struct does not store any other states or states pertaining to
@@ -58,6 +59,13 @@
         GameMode = 1u << 19,
         BufferUsageFlags = 1u << 20,
     };
+
+    static constexpr ftl::Flags<Changes> kMustComposite = Changes::Created | Changes::Destroyed |
+            Changes::Hierarchy | Changes::Geometry | Changes::Content | Changes::Input |
+            Changes::Z | Changes::Mirror | Changes::Parent | Changes::RelativeParent |
+            Changes::Metadata | Changes::Visibility | Changes::VisibleRegion | Changes::Buffer |
+            Changes::SidebandStream | Changes::Animation | Changes::BufferSize | Changes::GameMode |
+            Changes::BufferUsageFlags;
     static Rect reduce(const Rect& win, const Region& exclude);
     RequestedLayerState(const LayerCreationArgs&);
     void merge(const ResolvedComposerState&);
@@ -79,6 +87,7 @@
     aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
     bool hasValidRelativeParent() const;
     bool hasInputInfo() const;
+    bool needsInputInfo() const;
     bool hasBlur() const;
     bool hasFrameUpdate() const;
     bool hasReadyFrame() const;
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index d3d9509..a1e8213 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -19,9 +19,9 @@
 #define LOG_TAG "SurfaceFlinger"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <cutils/trace.h>
 #include <utils/Log.h>
-#include <utils/Trace.h>
 #include "FrontEnd/LayerLog.h"
 
 #include "TransactionHandler.h"
@@ -31,7 +31,7 @@
 void TransactionHandler::queueTransaction(TransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
     mPendingTransactionCount.fetch_add(1);
-    ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
+    SFTRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
 }
 
 void TransactionHandler::collectTransactions() {
@@ -71,7 +71,7 @@
     applyUnsignaledBufferTransaction(transactions, flushState);
 
     mPendingTransactionCount.fetch_sub(transactions.size());
-    ATRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
+    SFTRACE_INT("TransactionQueue", static_cast<int>(mPendingTransactionCount.load()));
     return transactions;
 }
 
@@ -83,7 +83,7 @@
 
     // only apply an unsignaled buffer transaction if it's the first one
     if (!transactions.empty()) {
-        ATRACE_NAME("fence unsignaled");
+        SFTRACE_NAME("fence unsignaled");
         return;
     }
 
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
index e5cca8f..4af27ab 100644
--- a/services/surfaceflinger/FrontEnd/Update.h
+++ b/services/surfaceflinger/FrontEnd/Update.h
@@ -22,28 +22,13 @@
 #include "RequestedLayerState.h"
 #include "TransactionState.h"
 
-namespace android {
-struct LayerCreatedState {
-    LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
-          : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
-    wp<Layer> layer;
-    // Indicates the initial parent of the created layer, only used for creating layer in
-    // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
-    wp<Layer> initialParent;
-    // Indicates whether the layer getting created should be added at root if there's no parent
-    // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
-    // be added offscreen.
-    bool addToRoot;
-};
-} // namespace android
-
 namespace android::surfaceflinger::frontend {
 
 // Atomic set of changes affecting layer state. These changes are queued in binder threads and
 // applied every vsync.
 struct Update {
     std::vector<TransactionState> transactions;
-    std::vector<LayerCreatedState> layerCreatedStates;
+    std::vector<sp<Layer>> legacyLayers;
     std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
     std::vector<LayerCreationArgs> layerCreationArgs;
     std::vector<std::pair<uint32_t, std::string /* debugName */>> destroyedHandles;
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.cpp b/services/surfaceflinger/HdrLayerInfoReporter.cpp
index 2788332..85921bb 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.cpp
+++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp
@@ -19,8 +19,8 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <inttypes.h>
-#include <utils/Trace.h>
 
 #include "HdrLayerInfoReporter.h"
 
@@ -29,7 +29,7 @@
 using base::StringAppendF;
 
 void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mHdrInfoHistory.size() == 0 || mHdrInfoHistory.back().info != info) {
         mHdrInfoHistory.next() = EventHistoryEntry{info};
     }
@@ -47,7 +47,7 @@
     }
 
     for (const auto& listener : toInvoke) {
-        ATRACE_NAME("invoking onHdrLayerInfoChanged");
+        SFTRACE_NAME("invoking onHdrLayerInfoChanged");
         listener->onHdrLayerInfoChanged(info.numberOfHdrLayers, info.maxW, info.maxH, info.flags,
                                         info.maxDesiredHdrSdrRatio);
     }
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.cpp b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
index dfb1c1e..2088635 100644
--- a/services/surfaceflinger/HdrSdrRatioOverlay.cpp
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
@@ -114,7 +114,7 @@
         ALOGE("%s: Failed to create buffer state layer", __func__);
         return;
     }
-    SurfaceComposerClient::Transaction()
+    createTransaction()
             .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
             .setTrustedOverlay(mSurfaceControl->get(), true)
             .apply();
@@ -130,7 +130,7 @@
 }
 
 void HdrSdrRatioOverlay::setLayerStack(ui::LayerStack stack) {
-    SurfaceComposerClient::Transaction().setLayerStack(mSurfaceControl->get(), stack).apply();
+    createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
 void HdrSdrRatioOverlay::setViewport(ui::Size viewport) {
@@ -141,7 +141,7 @@
     // set the ratio frame to the top right of the screen
     frame.offsetBy(viewport.width - frame.width(), height >> 4);
 
-    SurfaceComposerClient::Transaction()
+    createTransaction()
             .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
                        0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
             .setPosition(mSurfaceControl->get(), frame.left, frame.top)
@@ -167,7 +167,7 @@
         }
     }();
 
-    SurfaceComposerClient::Transaction().setTransform(mSurfaceControl->get(), transform).apply();
+    createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
     constexpr SkColor kMinRatioColor = SK_ColorBLUE;
     constexpr SkColor kMaxRatioColor = SK_ColorGREEN;
@@ -194,9 +194,21 @@
 
 void HdrSdrRatioOverlay::animate() {
     if (!std::isfinite(mCurrentHdrSdrRatio) || mCurrentHdrSdrRatio < 1.0f) return;
-    SurfaceComposerClient::Transaction()
+    createTransaction()
             .setBuffer(mSurfaceControl->get(), getOrCreateBuffers(mCurrentHdrSdrRatio))
             .apply();
 }
 
+SurfaceComposerClient::Transaction HdrSdrRatioOverlay::createTransaction() const {
+    constexpr float kFrameRate = 0.f;
+    constexpr int8_t kCompatibility = ANATIVEWINDOW_FRAME_RATE_NO_VOTE;
+    constexpr int8_t kSeamlessness = ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS;
+
+    const sp<SurfaceControl>& surface = mSurfaceControl->get();
+
+    SurfaceComposerClient::Transaction transaction;
+    transaction.setFrameRate(surface, kFrameRate, kCompatibility, kSeamlessness);
+    return transaction;
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.h b/services/surfaceflinger/HdrSdrRatioOverlay.h
index 72d401d..ba88252 100644
--- a/services/surfaceflinger/HdrSdrRatioOverlay.h
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.h
@@ -53,5 +53,7 @@
 
     size_t mIndex = 0;
     std::array<sp<GraphicBuffer>, 2> mRingBuffer;
+
+    SurfaceComposerClient::Transaction createTransaction() const;
 };
 } // namespace android
diff --git a/services/surfaceflinger/Jank/JankTracker.cpp b/services/surfaceflinger/Jank/JankTracker.cpp
new file mode 100644
index 0000000..8e0e084
--- /dev/null
+++ b/services/surfaceflinger/Jank/JankTracker.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2024 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 "JankTracker.h"
+
+#include <android/gui/IJankListener.h>
+#include "BackgroundExecutor.h"
+
+namespace android {
+
+namespace {
+
+constexpr size_t kJankDataBatchSize = 50;
+
+} // anonymous namespace
+
+std::atomic<size_t> JankTracker::sListenerCount(0);
+std::atomic<bool> JankTracker::sCollectAllJankDataForTesting(false);
+
+JankTracker::~JankTracker() {}
+
+void JankTracker::addJankListener(int32_t layerId, sp<IBinder> listener) {
+    // Increment right away, so that if an onJankData call comes in before the background thread has
+    // added this listener, it will not drop the data.
+    sListenerCount++;
+
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, listener = std::move(listener)]() {
+                JankTracker& tracker = getInstance();
+                const std::lock_guard<std::mutex> _l(tracker.mLock);
+                tracker.addJankListenerLocked(layerId, listener);
+            }});
+}
+
+void JankTracker::flushJankData(int32_t layerId) {
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId]() { getInstance().doFlushJankData(layerId); }});
+}
+
+void JankTracker::removeJankListener(int32_t layerId, sp<IBinder> listener, int64_t afterVsync) {
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, listener = std::move(listener), afterVsync]() {
+                JankTracker& tracker = getInstance();
+                const std::lock_guard<std::mutex> _l(tracker.mLock);
+                tracker.markJankListenerForRemovalLocked(layerId, listener, afterVsync);
+            }});
+}
+
+void JankTracker::onJankData(int32_t layerId, gui::JankData data) {
+    if (sListenerCount == 0) {
+        return;
+    }
+
+    BackgroundExecutor::getLowPriorityInstance().sendCallbacks(
+            {[layerId, data = std::move(data)]() {
+                JankTracker& tracker = getInstance();
+
+                tracker.mLock.lock();
+                bool hasListeners = tracker.mJankListeners.count(layerId) > 0;
+                tracker.mLock.unlock();
+
+                if (!hasListeners && !sCollectAllJankDataForTesting) {
+                    return;
+                }
+
+                tracker.mJankDataLock.lock();
+                tracker.mJankData.emplace(layerId, data);
+                size_t count = tracker.mJankData.count(layerId);
+                tracker.mJankDataLock.unlock();
+
+                if (count >= kJankDataBatchSize && !sCollectAllJankDataForTesting) {
+                    tracker.doFlushJankData(layerId);
+                }
+            }});
+}
+
+void JankTracker::addJankListenerLocked(int32_t layerId, sp<IBinder> listener) {
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            // Undo the duplicate increment in addJankListener.
+            sListenerCount--;
+            return;
+        }
+    }
+
+    mJankListeners.emplace(layerId, std::move(listener));
+}
+
+void JankTracker::doFlushJankData(int32_t layerId) {
+    std::vector<gui::JankData> jankData;
+    int64_t maxVsync = transferAvailableJankData(layerId, jankData);
+
+    std::vector<sp<IBinder>> toSend;
+
+    mLock.lock();
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end();) {
+        if (!jankData.empty()) {
+            toSend.emplace_back(it->second.mListener);
+        }
+
+        int64_t removeAfter = it->second.mRemoveAfter;
+        if (removeAfter != -1 && removeAfter <= maxVsync) {
+            it = mJankListeners.erase(it);
+            sListenerCount--;
+        } else {
+            it++;
+        }
+    }
+    mLock.unlock();
+
+    for (const auto& listener : toSend) {
+        binder::Status status = interface_cast<gui::IJankListener>(listener)->onJankData(jankData);
+        if (status.exceptionCode() == binder::Status::EX_NULL_POINTER) {
+            // Remove any listeners, where the App side has gone away, without
+            // deregistering.
+            dropJankListener(layerId, listener);
+        }
+    }
+}
+
+void JankTracker::markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener,
+                                                   int64_t afterVysnc) {
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            it->second.mRemoveAfter = std::max(static_cast<int64_t>(0), afterVysnc);
+            return;
+        }
+    }
+}
+
+int64_t JankTracker::transferAvailableJankData(int32_t layerId,
+                                               std::vector<gui::JankData>& outJankData) {
+    const std::lock_guard<std::mutex> _l(mJankDataLock);
+    int64_t maxVsync = 0;
+    auto range = mJankData.equal_range(layerId);
+    for (auto it = range.first; it != range.second;) {
+        maxVsync = std::max(it->second.frameVsyncId, maxVsync);
+        outJankData.emplace_back(std::move(it->second));
+        it = mJankData.erase(it);
+    }
+    return maxVsync;
+}
+
+void JankTracker::dropJankListener(int32_t layerId, sp<IBinder> listener) {
+    const std::lock_guard<std::mutex> _l(mLock);
+    for (auto it = mJankListeners.find(layerId); it != mJankListeners.end(); it++) {
+        if (it->second.mListener == listener) {
+            mJankListeners.erase(it);
+            sListenerCount--;
+            return;
+        }
+    }
+}
+
+void JankTracker::clearAndStartCollectingAllJankDataForTesting() {
+    BackgroundExecutor::getLowPriorityInstance().flushQueue();
+
+    // Clear all past tracked jank data.
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+    tracker.mJankData.clear();
+
+    // Pretend there's at least one listener.
+    sListenerCount++;
+    sCollectAllJankDataForTesting = true;
+}
+
+std::vector<gui::JankData> JankTracker::getCollectedJankDataForTesting(int32_t layerId) {
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+
+    auto range = tracker.mJankData.equal_range(layerId);
+    std::vector<gui::JankData> result;
+    std::transform(range.first, range.second, std::back_inserter(result),
+                   [](std::pair<int32_t, gui::JankData> layerIdToJankData) {
+                       return layerIdToJankData.second;
+                   });
+
+    return result;
+}
+
+void JankTracker::clearAndStopCollectingAllJankDataForTesting() {
+    // Undo startCollectingAllJankDataForTesting.
+    sListenerCount--;
+    sCollectAllJankDataForTesting = false;
+
+    // Clear all tracked jank data.
+    JankTracker& tracker = getInstance();
+    const std::lock_guard<std::mutex> _l(tracker.mJankDataLock);
+    tracker.mJankData.clear();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Jank/JankTracker.h b/services/surfaceflinger/Jank/JankTracker.h
new file mode 100644
index 0000000..5917358
--- /dev/null
+++ b/services/surfaceflinger/Jank/JankTracker.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <mutex>
+#include <unordered_map>
+
+#include <android/gui/JankData.h>
+#include <binder/IBinder.h>
+#include <utils/Mutex.h>
+
+namespace android {
+namespace frametimeline {
+class FrameTimelineTest;
+}
+
+/**
+ * JankTracker maintains a backlog of frame jank classification and manages and notififies any
+ * registered jank data listeners.
+ */
+class JankTracker {
+public:
+    ~JankTracker();
+
+    static void addJankListener(int32_t layerId, sp<IBinder> listener);
+    static void flushJankData(int32_t layerId);
+    static void removeJankListener(int32_t layerId, sp<IBinder> listener, int64_t afterVysnc);
+
+    static void onJankData(int32_t layerId, gui::JankData data);
+
+protected:
+    // The following methods can be used to force the tracker to collect all jank data and not
+    // flush it for a short time period and should *only* be used for testing. Every call to
+    // clearAndStartCollectingAllJankDataForTesting needs to be followed by a call to
+    // clearAndStopCollectingAllJankDataForTesting.
+    static void clearAndStartCollectingAllJankDataForTesting();
+    static std::vector<gui::JankData> getCollectedJankDataForTesting(int32_t layerId);
+    static void clearAndStopCollectingAllJankDataForTesting();
+
+    friend class frametimeline::FrameTimelineTest;
+
+private:
+    JankTracker() {}
+    JankTracker(const JankTracker&) = delete;
+    JankTracker(JankTracker&&) = delete;
+
+    JankTracker& operator=(const JankTracker&) = delete;
+    JankTracker& operator=(JankTracker&&) = delete;
+
+    static JankTracker& getInstance() {
+        static JankTracker instance;
+        return instance;
+    }
+
+    void addJankListenerLocked(int32_t layerId, sp<IBinder> listener) REQUIRES(mLock);
+    void doFlushJankData(int32_t layerId);
+    void markJankListenerForRemovalLocked(int32_t layerId, sp<IBinder> listener, int64_t afterVysnc)
+            REQUIRES(mLock);
+
+    int64_t transferAvailableJankData(int32_t layerId, std::vector<gui::JankData>& jankData);
+    void dropJankListener(int32_t layerId, sp<IBinder> listener);
+
+    struct Listener {
+        sp<IBinder> mListener;
+        int64_t mRemoveAfter;
+
+        Listener(sp<IBinder>&& listener) : mListener(listener), mRemoveAfter(-1) {}
+    };
+
+    // We keep track of the current listener count, so that the onJankData call, which is on the
+    // main thread, can short-curcuit the scheduling on the background thread (which involves
+    // locking) if there are no listeners registered, which is the most common case.
+    static std::atomic<size_t> sListenerCount;
+    static std::atomic<bool> sCollectAllJankDataForTesting;
+
+    std::mutex mLock;
+    std::unordered_multimap<int32_t, Listener> mJankListeners GUARDED_BY(mLock);
+    std::mutex mJankDataLock;
+    std::unordered_multimap<int32_t, gui::JankData> mJankData GUARDED_BY(mJankDataLock);
+
+    friend class JankTrackerTest;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 9c8887d..dcb0812 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -15,6 +15,7 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
+
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
@@ -23,11 +24,10 @@
 #define LOG_TAG "Layer"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include "Layer.h"
-
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <binder/IPCThreadState.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/LayerFECompositionState.h>
@@ -39,9 +39,7 @@
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <gui/BufferItem.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/Surface.h>
-#include <gui/TraceUtils.h>
 #include <math.h>
 #include <private/android_filesystem_config.h>
 #include <renderengine/RenderEngine.h>
@@ -60,12 +58,9 @@
 #include <utils/Log.h>
 #include <utils/NativeHandle.h>
 #include <utils/StopWatch.h>
-#include <utils/Trace.h>
 
 #include <algorithm>
-#include <mutex>
 #include <optional>
-#include <sstream>
 
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWComposer.h"
@@ -73,10 +68,11 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
 #include "LayerProtoHelper.h"
-#include "MutexUtils.h"
 #include "SurfaceFlinger.h"
 #include "TimeStats/TimeStats.h"
+#include "TransactionCallbackInvoker.h"
 #include "TunnelModeEnabledReporter.h"
 #include "Utils/FenceUtils.h"
 
@@ -90,14 +86,6 @@
 
 const ui::Transform kIdentityTransform;
 
-bool assignTransform(ui::Transform* dst, ui::Transform& from) {
-    if (*dst == from) {
-        return false;
-    }
-    *dst = from;
-    return true;
-}
-
 TimeStats::SetFrameRateVote frameRateToSetFrameRateVotePayload(Layer::FrameRate frameRate) {
     using FrameRateCompatibility = TimeStats::SetFrameRateVote::FrameRateCompatibility;
     using Seamlessness = TimeStats::SetFrameRateVote::Seamlessness;
@@ -146,25 +134,11 @@
       : sequence(args.sequence),
         mFlinger(sp<SurfaceFlinger>::fromExisting(args.flinger)),
         mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),
-        mClientRef(args.client),
         mWindowType(static_cast<WindowInfo::Type>(
-                args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
-        mLayerCreationFlags(args.flags),
-        mBorderEnabled(false),
-        mLegacyLayerFE(args.flinger->getFactory().createLayerFE(mName)) {
+                args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))) {
     ALOGV("Creating Layer %s", getDebugName());
 
-    uint32_t layerFlags = 0;
-    if (args.flags & ISurfaceComposerClient::eHidden) layerFlags |= layer_state_t::eLayerHidden;
-    if (args.flags & ISurfaceComposerClient::eOpaque) layerFlags |= layer_state_t::eLayerOpaque;
-    if (args.flags & ISurfaceComposerClient::eSecure) layerFlags |= layer_state_t::eLayerSecure;
-    if (args.flags & ISurfaceComposerClient::eSkipScreenshot)
-        layerFlags |= layer_state_t::eLayerSkipScreenshot;
-    mDrawingState.flags = layerFlags;
     mDrawingState.crop.makeInvalid();
-    mDrawingState.z = 0;
-    mDrawingState.color.a = 1.0f;
-    mDrawingState.layerStack = ui::DEFAULT_LAYER_STACK;
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
@@ -177,33 +151,9 @@
     mDrawingState.acquireFence = sp<Fence>::make(-1);
     mDrawingState.acquireFenceTime = std::make_shared<FenceTime>(mDrawingState.acquireFence);
     mDrawingState.dataspace = ui::Dataspace::V0_SRGB;
-    mDrawingState.hdrMetadata.validTypes = 0;
-    mDrawingState.surfaceDamageRegion = Region::INVALID_REGION;
-    mDrawingState.cornerRadius = 0.0f;
-    mDrawingState.backgroundBlurRadius = 0;
-    mDrawingState.api = -1;
-    mDrawingState.hasColorTransform = false;
-    mDrawingState.colorSpaceAgnostic = false;
-    mDrawingState.frameRateSelectionPriority = PRIORITY_UNSET;
     mDrawingState.metadata = args.metadata;
-    mDrawingState.shadowRadius = 0.f;
-    mDrawingState.fixedTransformHint = ui::Transform::ROT_INVALID;
     mDrawingState.frameTimelineInfo = {};
     mDrawingState.postTime = -1;
-    mDrawingState.destinationFrame.makeInvalid();
-    mDrawingState.isTrustedOverlay = false;
-    mDrawingState.dropInputMode = gui::DropInputMode::NONE;
-    mDrawingState.dimmingEnabled = true;
-    mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
-    mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Propagate;
-
-    if (args.flags & ISurfaceComposerClient::eNoColorFill) {
-        // Set an invalid color so there is no color fill.
-        mDrawingState.color.r = -1.0_hf;
-        mDrawingState.color.g = -1.0_hf;
-        mDrawingState.color.b = -1.0_hf;
-    }
-
     mFrameTracker.setDisplayRefreshPeriod(
             args.flinger->mScheduler->getPacesetterVsyncPeriod().ns());
 
@@ -211,14 +161,9 @@
     mOwnerPid = args.ownerPid;
     mOwnerAppId = mOwnerUid % PER_USER_RANGE;
 
-    mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
     mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
-    mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
-
-    mSnapshot->sequence = sequence;
-    mSnapshot->name = getDebugName();
-    mSnapshot->premultipliedAlpha = mPremultipliedAlpha;
-    mSnapshot->parentTransform = {};
+    mLayerFEs.emplace_back(frontend::LayerHierarchy::TraversalPath{static_cast<uint32_t>(sequence)},
+                           args.flinger->getFactory().createLayerFE(mName, this));
 }
 
 void Layer::onFirstRef() {
@@ -229,10 +174,6 @@
     LOG_ALWAYS_FATAL_IF(std::this_thread::get_id() != mFlinger->mMainThreadId,
                         "Layer destructor called off the main thread.");
 
-    // The original layer and the clone layer share the same texture and buffer. Therefore, only
-    // one of the layers, in this case the original layer, needs to handle the deletion. The
-    // original layer and the clone should be removed at the same time so there shouldn't be any
-    // issue with the clone layer trying to use the texture.
     if (mBufferInfo.mBuffer != nullptr) {
         callReleaseBufferCallback(mDrawingState.releaseBufferListener,
                                   mBufferInfo.mBuffer->getBuffer(), mBufferInfo.mFrameNumber,
@@ -248,10 +189,6 @@
     if (mDrawingState.sidebandStream != nullptr) {
         mFlinger->mTunnelModeEnabledReporter->decrementTunnelModeCount();
     }
-    if (mHadClonedChild) {
-        auto& roots = mFlinger->mLayerMirrorRoots;
-        roots.erase(std::remove(roots.begin(), roots.end(), this), roots.end());
-    }
     if (hasTrustedPresentationListener()) {
         mFlinger->mNumTrustedPresentationListeners--;
         updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
@@ -259,78 +196,8 @@
 }
 
 // ---------------------------------------------------------------------------
-// callbacks
-// ---------------------------------------------------------------------------
-
-void Layer::removeRelativeZ(const std::vector<Layer*>& layersInTree) {
-    if (mDrawingState.zOrderRelativeOf == nullptr) {
-        return;
-    }
-
-    sp<Layer> strongRelative = mDrawingState.zOrderRelativeOf.promote();
-    if (strongRelative == nullptr) {
-        setZOrderRelativeOf(nullptr);
-        return;
-    }
-
-    if (!std::binary_search(layersInTree.begin(), layersInTree.end(), strongRelative.get())) {
-        strongRelative->removeZOrderRelative(wp<Layer>::fromExisting(this));
-        mFlinger->setTransactionFlags(eTraversalNeeded);
-        setZOrderRelativeOf(nullptr);
-    }
-}
-
-void Layer::removeFromCurrentState() {
-    if (!mRemovedFromDrawingState) {
-        mRemovedFromDrawingState = true;
-        mFlinger->mScheduler->deregisterLayer(this);
-    }
-    updateTrustedPresentationState(nullptr, nullptr, -1 /* time_in_ms */, true /* leaveState*/);
-
-    mFlinger->markLayerPendingRemovalLocked(sp<Layer>::fromExisting(this));
-}
-
-sp<Layer> Layer::getRootLayer() {
-    sp<Layer> parent = getParent();
-    if (parent == nullptr) {
-        return sp<Layer>::fromExisting(this);
-    }
-    return parent->getRootLayer();
-}
-
-void Layer::onRemovedFromCurrentState() {
-    // Use the root layer since we want to maintain the hierarchy for the entire subtree.
-    auto layersInTree = getRootLayer()->getLayersInTree(LayerVector::StateSet::Current);
-    std::sort(layersInTree.begin(), layersInTree.end());
-
-    REQUIRE_MUTEX(mFlinger->mStateLock);
-    traverse(LayerVector::StateSet::Current,
-             [&](Layer* layer) REQUIRES(layer->mFlinger->mStateLock) {
-                 layer->removeFromCurrentState();
-                 layer->removeRelativeZ(layersInTree);
-             });
-}
-
-void Layer::addToCurrentState() {
-    if (mRemovedFromDrawingState) {
-        mRemovedFromDrawingState = false;
-        mFlinger->mScheduler->registerLayer(this);
-        mFlinger->removeFromOffscreenLayers(this);
-    }
-
-    for (const auto& child : mCurrentChildren) {
-        child->addToCurrentState();
-    }
-}
-
-// ---------------------------------------------------------------------------
 // set-up
 // ---------------------------------------------------------------------------
-
-bool Layer::getPremultipledAlpha() const {
-    return mPremultipliedAlpha;
-}
-
 sp<IBinder> Layer::getHandle() {
     Mutex::Autolock _l(mLock);
     if (mGetHandleCalled) {
@@ -346,46 +213,6 @@
 // h/w composer set-up
 // ---------------------------------------------------------------------------
 
-static Rect reduce(const Rect& win, const Region& exclude) {
-    if (CC_LIKELY(exclude.isEmpty())) {
-        return win;
-    }
-    if (exclude.isRect()) {
-        return win.reduce(exclude.getBounds());
-    }
-    return Region(win).subtract(exclude).getBounds();
-}
-
-static FloatRect reduce(const FloatRect& win, const Region& exclude) {
-    if (CC_LIKELY(exclude.isEmpty())) {
-        return win;
-    }
-    // Convert through Rect (by rounding) for lack of FloatRegion
-    return Region(Rect{win}).subtract(exclude).getBounds().toFloatRect();
-}
-
-Rect Layer::getScreenBounds(bool reduceTransparentRegion) const {
-    if (!reduceTransparentRegion) {
-        return Rect{mScreenBounds};
-    }
-
-    FloatRect bounds = getBounds();
-    ui::Transform t = getTransform();
-    // Transform to screen space.
-    bounds = t.transform(bounds);
-    return Rect{bounds};
-}
-
-FloatRect Layer::getBounds() const {
-    const State& s(getDrawingState());
-    return getBounds(getActiveTransparentRegion(s));
-}
-
-FloatRect Layer::getBounds(const Region& activeTransparentRegion) const {
-    // Subtract the transparent region and snap to the bounds.
-    return reduce(mBounds, activeTransparentRegion);
-}
-
 // No early returns.
 void Layer::updateTrustedPresentationState(const DisplayDevice* display,
                                            const frontend::LayerSnapshot* snapshot,
@@ -487,57 +314,6 @@
     return true;
 }
 
-void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform,
-                          float parentShadowRadius) {
-    const State& s(getDrawingState());
-
-    // Calculate effective layer transform
-    mEffectiveTransform = parentTransform * getActiveTransform(s);
-
-    if (CC_UNLIKELY(!isTransformValid())) {
-        ALOGW("Stop computing bounds for %s because it has invalid transformation.",
-              getDebugName());
-        return;
-    }
-
-    // Transform parent bounds to layer space
-    parentBounds = getActiveTransform(s).inverse().transform(parentBounds);
-
-    // Calculate source bounds
-    mSourceBounds = computeSourceBounds(parentBounds);
-
-    // Calculate bounds by croping diplay frame with layer crop and parent bounds
-    FloatRect bounds = mSourceBounds;
-    const Rect layerCrop = getCrop(s);
-    if (!layerCrop.isEmpty()) {
-        bounds = mSourceBounds.intersect(layerCrop.toFloatRect());
-    }
-    bounds = bounds.intersect(parentBounds);
-
-    mBounds = bounds;
-    mScreenBounds = mEffectiveTransform.transform(mBounds);
-
-    // Use the layer's own shadow radius if set. Otherwise get the radius from
-    // parent.
-    if (s.shadowRadius > 0.f) {
-        mEffectiveShadowRadius = s.shadowRadius;
-    } else {
-        mEffectiveShadowRadius = parentShadowRadius;
-    }
-
-    // Shadow radius is passed down to only one layer so if the layer can draw shadows,
-    // don't pass it to its children.
-    const float childShadowRadius = canDrawShadows() ? 0.f : mEffectiveShadowRadius;
-
-    for (const sp<Layer>& child : mDrawingChildren) {
-        child->computeBounds(mBounds, mEffectiveTransform, childShadowRadius);
-    }
-
-    if (mPotentialCursor) {
-        prepareCursorCompositionState();
-    }
-}
-
 Rect Layer::getCroppedBufferSize(const State& s) const {
     Rect size = getBufferSize(s);
     Rect crop = getCrop(s);
@@ -549,181 +325,6 @@
     return size;
 }
 
-void Layer::setupRoundedCornersCropCoordinates(Rect win,
-                                               const FloatRect& roundedCornersCrop) const {
-    // Translate win by the rounded corners rect coordinates, to have all values in
-    // layer coordinate space.
-    win.left -= roundedCornersCrop.left;
-    win.right -= roundedCornersCrop.left;
-    win.top -= roundedCornersCrop.top;
-    win.bottom -= roundedCornersCrop.top;
-}
-
-void Layer::prepareBasicGeometryCompositionState() {
-    const auto& drawingState{getDrawingState()};
-    const auto alpha = static_cast<float>(getAlpha());
-    const bool opaque = isOpaque(drawingState);
-    const bool usesRoundedCorners = hasRoundedCorners();
-
-    auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
-    if (!opaque || alpha != 1.0f) {
-        blendMode = mPremultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED
-                                        : Hwc2::IComposerClient::BlendMode::COVERAGE;
-    }
-
-    // Please keep in sync with LayerSnapshotBuilder
-    auto* snapshot = editLayerSnapshot();
-    snapshot->outputFilter = getOutputFilter();
-    snapshot->isVisible = isVisible();
-    snapshot->isOpaque = opaque && !usesRoundedCorners && alpha == 1.f;
-    snapshot->shadowSettings.length = mEffectiveShadowRadius;
-
-    snapshot->contentDirty = contentDirty;
-    contentDirty = false;
-
-    snapshot->geomLayerBounds = mBounds;
-    snapshot->geomLayerTransform = getTransform();
-    snapshot->geomInverseLayerTransform = snapshot->geomLayerTransform.inverse();
-    snapshot->transparentRegionHint = getActiveTransparentRegion(drawingState);
-    snapshot->localTransform = getActiveTransform(drawingState);
-    snapshot->localTransformInverse = snapshot->localTransform.inverse();
-    snapshot->blendMode = static_cast<Hwc2::IComposerClient::BlendMode>(blendMode);
-    snapshot->alpha = alpha;
-    snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
-    snapshot->blurRegions = getBlurRegions();
-    snapshot->stretchEffect = getStretchEffect();
-}
-
-void Layer::prepareGeometryCompositionState() {
-    const auto& drawingState{getDrawingState()};
-    auto* snapshot = editLayerSnapshot();
-
-    // Please keep in sync with LayerSnapshotBuilder
-    snapshot->geomBufferSize = getBufferSize(drawingState);
-    snapshot->geomContentCrop = getBufferCrop();
-    snapshot->geomCrop = getCrop(drawingState);
-    snapshot->geomBufferTransform = getBufferTransform();
-    snapshot->geomBufferUsesDisplayInverseTransform = getTransformToDisplayInverse();
-    snapshot->geomUsesSourceCrop = usesSourceCrop();
-    snapshot->isSecure = isSecure();
-
-    snapshot->metadata.clear();
-    const auto& supportedMetadata = mFlinger->getHwComposer().getSupportedLayerGenericMetadata();
-    for (const auto& [key, mandatory] : supportedMetadata) {
-        const auto& genericLayerMetadataCompatibilityMap =
-                mFlinger->getGenericLayerMetadataKeyMap();
-        auto compatIter = genericLayerMetadataCompatibilityMap.find(key);
-        if (compatIter == std::end(genericLayerMetadataCompatibilityMap)) {
-            continue;
-        }
-        const uint32_t id = compatIter->second;
-
-        auto it = drawingState.metadata.mMap.find(id);
-        if (it == std::end(drawingState.metadata.mMap)) {
-            continue;
-        }
-
-        snapshot->metadata.emplace(key,
-                                   compositionengine::GenericLayerMetadataEntry{mandatory,
-                                                                                it->second});
-    }
-}
-
-void Layer::preparePerFrameCompositionState() {
-    const auto& drawingState{getDrawingState()};
-    // Please keep in sync with LayerSnapshotBuilder
-    auto* snapshot = editLayerSnapshot();
-
-    snapshot->forceClientComposition = false;
-
-    snapshot->isColorspaceAgnostic = isColorSpaceAgnostic();
-    snapshot->dataspace = getDataSpace();
-    snapshot->colorTransform = getColorTransform();
-    snapshot->colorTransformIsIdentity = !hasColorTransform();
-    snapshot->surfaceDamage = surfaceDamageRegion;
-    snapshot->hasProtectedContent = isProtected();
-    snapshot->dimmingEnabled = isDimmingEnabled();
-    snapshot->currentHdrSdrRatio = getCurrentHdrSdrRatio();
-    snapshot->desiredHdrSdrRatio = getDesiredHdrSdrRatio();
-    snapshot->cachingHint = getCachingHint();
-
-    const bool usesRoundedCorners = hasRoundedCorners();
-
-    snapshot->isOpaque = isOpaque(drawingState) && !usesRoundedCorners && getAlpha() == 1.0_hf;
-
-    // Force client composition for special cases known only to the front-end.
-    // Rounded corners no longer force client composition, since we may use a
-    // hole punch so that the layer will appear to have rounded corners.
-    if (drawShadows() || snapshot->stretchEffect.hasEffect()) {
-        snapshot->forceClientComposition = true;
-    }
-    // If there are no visible region changes, we still need to update blur parameters.
-    snapshot->blurRegions = getBlurRegions();
-    snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
-
-    // Layer framerate is used in caching decisions.
-    // Retrieve it from the scheduler which maintains an instance of LayerHistory, and store it in
-    // LayerFECompositionState where it would be visible to Flattener.
-    snapshot->fps = mFlinger->getLayerFramerate(systemTime(), getSequence());
-
-    if (hasBufferOrSidebandStream()) {
-        preparePerFrameBufferCompositionState();
-    } else {
-        preparePerFrameEffectsCompositionState();
-    }
-}
-
-void Layer::preparePerFrameBufferCompositionState() {
-    // Please keep in sync with LayerSnapshotBuilder
-    auto* snapshot = editLayerSnapshot();
-    // Sideband layers
-    if (snapshot->sidebandStream.get() && !snapshot->sidebandStreamHasFrame) {
-        snapshot->compositionType =
-                aidl::android::hardware::graphics::composer3::Composition::SIDEBAND;
-        return;
-    } else if ((mDrawingState.flags & layer_state_t::eLayerIsDisplayDecoration) != 0) {
-        snapshot->compositionType =
-                aidl::android::hardware::graphics::composer3::Composition::DISPLAY_DECORATION;
-    } else if ((mDrawingState.flags & layer_state_t::eLayerIsRefreshRateIndicator) != 0) {
-        snapshot->compositionType =
-                aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR;
-    } else {
-        // Normal buffer layers
-        snapshot->hdrMetadata = mBufferInfo.mHdrMetadata;
-        snapshot->compositionType = mPotentialCursor
-                ? aidl::android::hardware::graphics::composer3::Composition::CURSOR
-                : aidl::android::hardware::graphics::composer3::Composition::DEVICE;
-    }
-
-    snapshot->buffer = getBuffer();
-    snapshot->acquireFence = mBufferInfo.mFence;
-    snapshot->frameNumber = mBufferInfo.mFrameNumber;
-    snapshot->sidebandStreamHasFrame = false;
-}
-
-void Layer::preparePerFrameEffectsCompositionState() {
-    // Please keep in sync with LayerSnapshotBuilder
-    auto* snapshot = editLayerSnapshot();
-    snapshot->color = getColor();
-    snapshot->compositionType =
-            aidl::android::hardware::graphics::composer3::Composition::SOLID_COLOR;
-}
-
-void Layer::prepareCursorCompositionState() {
-    const State& drawingState{getDrawingState()};
-    // Please keep in sync with LayerSnapshotBuilder
-    auto* snapshot = editLayerSnapshot();
-
-    // Apply the layer's transform, followed by the display's global transform
-    // Here we're guaranteed that the layer's transform preserves rects
-    Rect win = getCroppedBufferSize(drawingState);
-    // Subtract the transparent region and snap to the bounds
-    Rect bounds = reduce(win, getActiveTransparentRegion(drawingState));
-    Rect frame(getTransform().transform(bounds));
-
-    snapshot->cursorFrame = frame;
-}
-
 const char* Layer::getDebugName() const {
     return mName.c_str();
 }
@@ -751,91 +352,9 @@
 }
 
 // ----------------------------------------------------------------------------
-// local state
-// ----------------------------------------------------------------------------
-
-bool Layer::isSecure() const {
-    const State& s(mDrawingState);
-    if (s.flags & layer_state_t::eLayerSecure) {
-        return true;
-    }
-
-    const auto p = mDrawingParent.promote();
-    return (p != nullptr) ? p->isSecure() : false;
-}
-
-void Layer::transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
-                                      std::vector<JankData>& jankData) {
-    if (mPendingJankClassifications.empty() ||
-        !mPendingJankClassifications.front()->getJankType()) {
-        return;
-    }
-
-    bool includeJankData = false;
-    for (const auto& handle : handles) {
-        for (const auto& cb : handle->callbackIds) {
-            if (cb.includeJankData) {
-                includeJankData = true;
-                break;
-            }
-        }
-
-        if (includeJankData) {
-            jankData.reserve(mPendingJankClassifications.size());
-            break;
-        }
-    }
-
-    while (!mPendingJankClassifications.empty() &&
-           mPendingJankClassifications.front()->getJankType()) {
-        if (includeJankData) {
-            std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
-                    mPendingJankClassifications.front();
-            jankData.emplace_back(JankData(surfaceFrame->getToken(),
-                                           surfaceFrame->getJankType().value(),
-                                           surfaceFrame->getRenderRate().getPeriodNsecs()));
-        }
-        mPendingJankClassifications.pop_front();
-    }
-}
-
-// ----------------------------------------------------------------------------
 // transaction
 // ----------------------------------------------------------------------------
 
-uint32_t Layer::doTransaction(uint32_t flags) {
-    ATRACE_CALL();
-
-    // TODO: This is unfortunate.
-    mDrawingStateModified = mDrawingState.modified;
-    mDrawingState.modified = false;
-
-    const State& s(getDrawingState());
-
-    if (updateGeometry()) {
-        // invalidate and recompute the visible regions if needed
-        flags |= Layer::eVisibleRegion;
-    }
-
-    if (s.sequence != mLastCommittedTxSequence) {
-        // invalidate and recompute the visible regions if needed
-        mLastCommittedTxSequence = s.sequence;
-        flags |= eVisibleRegion;
-        this->contentDirty = true;
-
-        // we may use linear filtering, if the matrix scales us
-        mNeedsFiltering = getActiveTransform(s).needsBilinearFiltering();
-    }
-
-    if (!mPotentialCursor && (flags & Layer::eVisibleRegion)) {
-        mFlinger->mUpdateInputInfo = true;
-    }
-
-    commitTransaction();
-
-    return flags;
-}
-
 void Layer::commitTransaction() {
     // Set the present state for all bufferlessSurfaceFramesTX to Presented. The
     // bufferSurfaceFrameTX will be presented in latchBuffer.
@@ -850,526 +369,25 @@
     mDrawingState.bufferlessSurfaceFramesTX.clear();
 }
 
-uint32_t Layer::clearTransactionFlags(uint32_t mask) {
-    const auto flags = mTransactionFlags & mask;
-    mTransactionFlags &= ~mask;
-    return flags;
-}
-
 void Layer::setTransactionFlags(uint32_t mask) {
     mTransactionFlags |= mask;
 }
 
-bool Layer::setChildLayer(const sp<Layer>& childLayer, int32_t z) {
-    ssize_t idx = mCurrentChildren.indexOf(childLayer);
-    if (idx < 0) {
-        return false;
-    }
-    if (childLayer->setLayer(z)) {
-        mCurrentChildren.removeAt(idx);
-        mCurrentChildren.add(childLayer);
-        return true;
-    }
-    return false;
-}
-
-bool Layer::setChildRelativeLayer(const sp<Layer>& childLayer,
-        const sp<IBinder>& relativeToHandle, int32_t relativeZ) {
-    ssize_t idx = mCurrentChildren.indexOf(childLayer);
-    if (idx < 0) {
-        return false;
-    }
-    if (childLayer->setRelativeLayer(relativeToHandle, relativeZ)) {
-        mCurrentChildren.removeAt(idx);
-        mCurrentChildren.add(childLayer);
-        return true;
-    }
-    return false;
-}
-
-bool Layer::setLayer(int32_t z) {
-    if (mDrawingState.z == z && !usingRelativeZ(LayerVector::StateSet::Current)) return false;
-    mDrawingState.sequence++;
-    mDrawingState.z = z;
-    mDrawingState.modified = true;
-
-    mFlinger->mSomeChildrenChanged = true;
-
-    // Discard all relative layering.
-    if (mDrawingState.zOrderRelativeOf != nullptr) {
-        sp<Layer> strongRelative = mDrawingState.zOrderRelativeOf.promote();
-        if (strongRelative != nullptr) {
-            strongRelative->removeZOrderRelative(wp<Layer>::fromExisting(this));
-        }
-        setZOrderRelativeOf(nullptr);
-    }
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-void Layer::removeZOrderRelative(const wp<Layer>& relative) {
-    mDrawingState.zOrderRelatives.remove(relative);
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-}
-
-void Layer::addZOrderRelative(const wp<Layer>& relative) {
-    mDrawingState.zOrderRelatives.add(relative);
-    mDrawingState.modified = true;
-    mDrawingState.sequence++;
-    setTransactionFlags(eTransactionNeeded);
-}
-
-void Layer::setZOrderRelativeOf(const wp<Layer>& relativeOf) {
-    mDrawingState.zOrderRelativeOf = relativeOf;
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    mDrawingState.isRelativeOf = relativeOf != nullptr;
-
-    setTransactionFlags(eTransactionNeeded);
-}
-
-bool Layer::setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relativeZ) {
-    sp<Layer> relative = LayerHandle::getLayer(relativeToHandle);
-    if (relative == nullptr) {
-        return false;
-    }
-
-    if (mDrawingState.z == relativeZ && usingRelativeZ(LayerVector::StateSet::Current) &&
-        mDrawingState.zOrderRelativeOf == relative) {
-        return false;
-    }
-
-    if (CC_UNLIKELY(relative->usingRelativeZ(LayerVector::StateSet::Drawing)) &&
-        (relative->mDrawingState.zOrderRelativeOf == this)) {
-        ALOGE("Detected relative layer loop between %s and %s",
-              mName.c_str(), relative->mName.c_str());
-        ALOGE("Ignoring new call to set relative layer");
-        return false;
-    }
-
-    mFlinger->mSomeChildrenChanged = true;
-
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    mDrawingState.z = relativeZ;
-
-    auto oldZOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote();
-    if (oldZOrderRelativeOf != nullptr) {
-        oldZOrderRelativeOf->removeZOrderRelative(wp<Layer>::fromExisting(this));
-    }
-    setZOrderRelativeOf(relative);
-    relative->addZOrderRelative(wp<Layer>::fromExisting(this));
-
-    setTransactionFlags(eTransactionNeeded);
-
-    return true;
-}
-
-bool Layer::setTrustedOverlay(bool isTrustedOverlay) {
-    if (mDrawingState.isTrustedOverlay == isTrustedOverlay) return false;
-    mDrawingState.isTrustedOverlay = isTrustedOverlay;
-    mDrawingState.modified = true;
-    mFlinger->mUpdateInputInfo = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::isTrustedOverlay() const {
-    if (getDrawingState().isTrustedOverlay) {
-        return true;
-    }
-    const auto& p = mDrawingParent.promote();
-    return (p != nullptr) && p->isTrustedOverlay();
-}
-
-bool Layer::setAlpha(float alpha) {
-    if (mDrawingState.color.a == alpha) return false;
-    mDrawingState.sequence++;
-    mDrawingState.color.a = alpha;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace) {
-    if (!mDrawingState.bgColorLayer && alpha == 0) {
-        return false;
-    }
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    if (!mDrawingState.bgColorLayer && alpha != 0) {
-        // create background color layer if one does not yet exist
-        uint32_t flags = ISurfaceComposerClient::eFXSurfaceEffect;
-        std::string name = mName + "BackgroundColorLayer";
-        mDrawingState.bgColorLayer = mFlinger->getFactory().createEffectLayer(
-                surfaceflinger::LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
-                                                  LayerMetadata()));
-
-        // add to child list
-        addChild(mDrawingState.bgColorLayer);
-        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;
-    }
-
-    mDrawingState.bgColorLayer->setColor(color);
-    mDrawingState.bgColorLayer->setLayer(std::numeric_limits<int32_t>::min());
-    mDrawingState.bgColorLayer->setAlpha(alpha);
-    mDrawingState.bgColorLayer->setDataspace(dataspace);
-
-    return true;
-}
-
-bool Layer::setCornerRadius(float cornerRadius) {
-    if (mDrawingState.cornerRadius == cornerRadius) return false;
-
-    mDrawingState.sequence++;
-    mDrawingState.cornerRadius = cornerRadius;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setBackgroundBlurRadius(int backgroundBlurRadius) {
-    if (mDrawingState.backgroundBlurRadius == backgroundBlurRadius) return false;
-    // If we start or stop drawing blur then the layer's visibility state may change so increment
-    // the magic sequence number.
-    if (mDrawingState.backgroundBlurRadius == 0 || backgroundBlurRadius == 0) {
-        mDrawingState.sequence++;
-    }
-    mDrawingState.backgroundBlurRadius = backgroundBlurRadius;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setTransparentRegionHint(const Region& transparent) {
-    mDrawingState.sequence++;
-    mDrawingState.transparentRegionHint = transparent;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setBlurRegions(const std::vector<BlurRegion>& blurRegions) {
-    // If we start or stop drawing blur then the layer's visibility state may change so increment
-    // the magic sequence number.
-    if (mDrawingState.blurRegions.size() == 0 || blurRegions.size() == 0) {
-        mDrawingState.sequence++;
-    }
-    mDrawingState.blurRegions = blurRegions;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setFlags(uint32_t flags, uint32_t mask) {
-    const uint32_t newFlags = (mDrawingState.flags & ~mask) | (flags & mask);
-    if (mDrawingState.flags == newFlags) return false;
-    mDrawingState.sequence++;
-    mDrawingState.flags = newFlags;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
 bool Layer::setCrop(const Rect& crop) {
     if (mDrawingState.crop == crop) return false;
     mDrawingState.sequence++;
     mDrawingState.crop = crop;
 
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
 
-bool Layer::setMetadata(const LayerMetadata& data) {
-    if (!mDrawingState.metadata.merge(data, true /* eraseEmpty */)) return false;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setLayerStack(ui::LayerStack layerStack) {
-    if (mDrawingState.layerStack == layerStack) return false;
-    mDrawingState.sequence++;
-    mDrawingState.layerStack = layerStack;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setColorSpaceAgnostic(const bool agnostic) {
-    if (mDrawingState.colorSpaceAgnostic == agnostic) {
-        return false;
-    }
-    mDrawingState.sequence++;
-    mDrawingState.colorSpaceAgnostic = agnostic;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setDimmingEnabled(const bool dimmingEnabled) {
-    if (mDrawingState.dimmingEnabled == dimmingEnabled) return false;
-
-    mDrawingState.sequence++;
-    mDrawingState.dimmingEnabled = dimmingEnabled;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setFrameRateSelectionPriority(int32_t priority) {
-    if (mDrawingState.frameRateSelectionPriority == priority) return false;
-    mDrawingState.frameRateSelectionPriority = priority;
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-int32_t Layer::getFrameRateSelectionPriority() const {
-    // Check if layer has priority set.
-    if (mDrawingState.frameRateSelectionPriority != PRIORITY_UNSET) {
-        return mDrawingState.frameRateSelectionPriority;
-    }
-    // If not, search whether its parents have it set.
-    sp<Layer> parent = getParent();
-    if (parent != nullptr) {
-        return parent->getFrameRateSelectionPriority();
-    }
-
-    return Layer::PRIORITY_UNSET;
-}
-
-bool Layer::setDefaultFrameRateCompatibility(FrameRateCompatibility compatibility) {
-    if (mDrawingState.defaultFrameRateCompatibility == compatibility) return false;
-    mDrawingState.defaultFrameRateCompatibility = compatibility;
-    mDrawingState.modified = true;
-    mFlinger->mScheduler->setDefaultFrameRateCompatibility(sequence, compatibility);
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-scheduler::FrameRateCompatibility Layer::getDefaultFrameRateCompatibility() const {
-    return mDrawingState.defaultFrameRateCompatibility;
-}
-
 bool Layer::isLayerFocusedBasedOnPriority(int32_t priority) {
     return priority == PRIORITY_FOCUSED_WITH_MODE || priority == PRIORITY_FOCUSED_WITHOUT_MODE;
 };
 
-ui::LayerStack Layer::getLayerStack(LayerVector::StateSet state) const {
-    bool useDrawing = state == LayerVector::StateSet::Drawing;
-    const auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
-    if (parent) {
-        return parent->getLayerStack();
-    }
-    return getDrawingState().layerStack;
-}
-
-bool Layer::setShadowRadius(float shadowRadius) {
-    if (mDrawingState.shadowRadius == shadowRadius) {
-        return false;
-    }
-
-    mDrawingState.sequence++;
-    mDrawingState.shadowRadius = shadowRadius;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint) {
-    if (mDrawingState.fixedTransformHint == fixedTransformHint) {
-        return false;
-    }
-
-    mDrawingState.sequence++;
-    mDrawingState.fixedTransformHint = fixedTransformHint;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setStretchEffect(const StretchEffect& effect) {
-    StretchEffect temp = effect;
-    temp.sanitize();
-    if (mDrawingState.stretchEffect == temp) {
-        return false;
-    }
-    mDrawingState.sequence++;
-    mDrawingState.stretchEffect = temp;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-StretchEffect Layer::getStretchEffect() const {
-    if (mDrawingState.stretchEffect.hasEffect()) {
-        return mDrawingState.stretchEffect;
-    }
-
-    sp<Layer> parent = getParent();
-    if (parent != nullptr) {
-        auto effect = parent->getStretchEffect();
-        if (effect.hasEffect()) {
-            // TODO(b/179047472): Map it? Or do we make the effect be in global space?
-            return effect;
-        }
-    }
-    return StretchEffect{};
-}
-
-bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) {
-    if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) {
-        return false;
-    }
-    mBorderEnabled = shouldEnable;
-    mBorderWidth = width;
-    mBorderColor = color;
-    return true;
-}
-
-bool Layer::isBorderEnabled() {
-    return mBorderEnabled;
-}
-
-float Layer::getBorderWidth() {
-    return mBorderWidth;
-}
-
-const half4& Layer::getBorderColor() {
-    return mBorderColor;
-}
-
-bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
-                                           bool* transactionNeeded) {
-    // Gets the frame rate to propagate to children.
-    const auto frameRate = [&] {
-        if (overrideChildren && parentFrameRate.isValid()) {
-            return parentFrameRate;
-        }
-
-        if (mDrawingState.frameRate.isValid()) {
-            return mDrawingState.frameRate;
-        }
-
-        return parentFrameRate;
-    }();
-
-    auto now = systemTime();
-    *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate, now);
-
-    // The frame rate is propagated to the children by default, but some properties may override it.
-    bool childrenHaveFrameRate = false;
-    const bool overrideChildrenFrameRate = overrideChildren || shouldOverrideChildrenFrameRate();
-    const bool canPropagateFrameRate = shouldPropagateFrameRate() || overrideChildrenFrameRate;
-    for (const sp<Layer>& child : mCurrentChildren) {
-        childrenHaveFrameRate |=
-                child->propagateFrameRateForLayerTree(canPropagateFrameRate ? frameRate
-                                                                            : FrameRate(),
-                                                      overrideChildrenFrameRate, transactionNeeded);
-    }
-
-    // If we don't have a valid frame rate specification, but the children do, we set this
-    // layer as NoVote to allow the children to control the refresh rate
-    if (!frameRate.isValid() && childrenHaveFrameRate) {
-        *transactionNeeded |=
-                setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote),
-                                               now);
-    }
-
-    // We return whether this layer or its children has a vote. We ignore ExactOrMultiple votes for
-    // the same reason we are allowing touch boost for those layers. See
-    // RefreshRateSelector::rankFrameRates for details.
-    const auto layerVotedWithDefaultCompatibility =
-            frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Default;
-    const auto layerVotedWithNoVote = frameRate.vote.type == FrameRateCompatibility::NoVote;
-    const auto layerVotedWithCategory = frameRate.category != FrameRateCategory::Default;
-    const auto layerVotedWithExactCompatibility =
-            frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Exact;
-    return layerVotedWithDefaultCompatibility || layerVotedWithNoVote || layerVotedWithCategory ||
-            layerVotedWithExactCompatibility || childrenHaveFrameRate;
-}
-
-void Layer::updateTreeHasFrameRateVote() {
-    const auto root = [&]() -> sp<Layer> {
-        sp<Layer> layer = sp<Layer>::fromExisting(this);
-        while (auto parent = layer->getParent()) {
-            layer = parent;
-        }
-        return layer;
-    }();
-
-    bool transactionNeeded = false;
-    root->propagateFrameRateForLayerTree({}, false, &transactionNeeded);
-
-    // TODO(b/195668952): we probably don't need eTraversalNeeded here
-    if (transactionNeeded) {
-        mFlinger->setTransactionFlags(eTraversalNeeded);
-    }
-}
-
-bool Layer::setFrameRate(FrameRate::FrameRateVote frameRateVote) {
-    if (mDrawingState.frameRate.vote == frameRateVote) {
-        return false;
-    }
-
-    mDrawingState.sequence++;
-    mDrawingState.frameRate.vote = frameRateVote;
-    mDrawingState.modified = true;
-
-    updateTreeHasFrameRateVote();
-
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setFrameRateCategory(FrameRateCategory category, bool smoothSwitchOnly) {
-    if (mDrawingState.frameRate.category == category &&
-        mDrawingState.frameRate.categorySmoothSwitchOnly == smoothSwitchOnly) {
-        return false;
-    }
-
-    mDrawingState.sequence++;
-    mDrawingState.frameRate.category = category;
-    mDrawingState.frameRate.categorySmoothSwitchOnly = smoothSwitchOnly;
-    mDrawingState.modified = true;
-
-    updateTreeHasFrameRateVote();
-
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
-    if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
-    mDrawingState.frameRateSelectionStrategy = strategy;
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-
-    updateTreeHasFrameRateVote();
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
 void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
-                                                      nsecs_t postTime) {
+                                                      nsecs_t postTime, gui::GameMode gameMode) {
     mDrawingState.postTime = postTime;
 
     // Check if one of the bufferlessSurfaceFramesTX contains the same vsyncId. This can happen if
@@ -1385,17 +403,17 @@
         mDrawingState.bufferSurfaceFrameTX->setActualQueueTime(postTime);
     } else {
         mDrawingState.bufferSurfaceFrameTX =
-                createSurfaceFrameForBuffer(info, postTime, mTransactionName);
+                createSurfaceFrameForBuffer(info, postTime, mTransactionName, gameMode);
     }
 
-    setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
+    setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName, gameMode);
 }
 
 void Layer::setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
-                                                          nsecs_t postTime) {
+                                                          nsecs_t postTime,
+                                                          gui::GameMode gameMode) {
     mDrawingState.frameTimelineInfo = info;
     mDrawingState.postTime = postTime;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
 
     if (const auto& bufferSurfaceFrameTX = mDrawingState.bufferSurfaceFrameTX;
@@ -1411,17 +429,17 @@
     // targeting different vsyncs).
     auto it = mDrawingState.bufferlessSurfaceFramesTX.find(info.vsyncId);
     if (it == mDrawingState.bufferlessSurfaceFramesTX.end()) {
-        auto surfaceFrame = createSurfaceFrameForTransaction(info, postTime);
+        auto surfaceFrame = createSurfaceFrameForTransaction(info, postTime, gameMode);
         mDrawingState.bufferlessSurfaceFramesTX[info.vsyncId] = surfaceFrame;
     } else {
         if (it->second->getPresentState() == PresentState::Presented) {
             // If the SurfaceFrame was already presented, its safe to overwrite it since it must
             // have been from previous vsync.
-            it->second = createSurfaceFrameForTransaction(info, postTime);
+            it->second = createSurfaceFrameForTransaction(info, postTime, gameMode);
         }
     }
 
-    setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
+    setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName, gameMode);
 }
 
 void Layer::addSurfaceFrameDroppedForBuffer(
@@ -1441,12 +459,12 @@
 }
 
 std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForTransaction(
-        const FrameTimelineInfo& info, nsecs_t postTime) {
+        const FrameTimelineInfo& info, nsecs_t postTime, gui::GameMode gameMode) {
     auto surfaceFrame =
             mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid,
                                                                  getSequence(), mName,
                                                                  mTransactionName,
-                                                                 /*isBuffer*/ false, getGameMode());
+                                                                 /*isBuffer*/ false, gameMode);
     surfaceFrame->setActualStartTime(info.startTimeNanos);
     // For Transactions, the post time is considered to be both queue and acquire fence time.
     surfaceFrame->setActualQueueTime(postTime);
@@ -1455,16 +473,16 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     return surfaceFrame;
 }
 
 std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForBuffer(
-        const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName) {
+        const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName,
+        gui::GameMode gameMode) {
     auto surfaceFrame =
             mFlinger->mFrameTimeline->createSurfaceFrameForToken(info, mOwnerPid, mOwnerUid,
                                                                  getSequence(), mName, debugName,
-                                                                 /*isBuffer*/ true, getGameMode());
+                                                                 /*isBuffer*/ true, gameMode);
     surfaceFrame->setActualStartTime(info.startTimeNanos);
     // For buffers, acquire fence time will set during latch.
     surfaceFrame->setActualQueueTime(queueTime);
@@ -1472,12 +490,11 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     return surfaceFrame;
 }
 
 void Layer::setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
-                                                  std::string debugName) {
+                                                  std::string debugName, gui::GameMode gameMode) {
     if (info.skippedFrameVsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
         return;
     }
@@ -1489,7 +506,7 @@
             mFlinger->mFrameTimeline->createSurfaceFrameForToken(skippedFrameTimelineInfo,
                                                                  mOwnerPid, mOwnerUid,
                                                                  getSequence(), mName, debugName,
-                                                                 /*isBuffer*/ false, getGameMode());
+                                                                 /*isBuffer*/ false, gameMode);
     surfaceFrame->setActualStartTime(skippedFrameTimelineInfo.skippedFrameStartTimeNanos);
     // For Transactions, the post time is considered to be both queue and acquire fence time.
     surfaceFrame->setActualQueueTime(postTime);
@@ -1498,29 +515,9 @@
     if (fps) {
         surfaceFrame->setRenderRate(*fps);
     }
-    onSurfaceFrameCreated(surfaceFrame);
     addSurfaceFrameDroppedForBuffer(surfaceFrame, postTime);
 }
 
-bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate, nsecs_t now) {
-    if (mDrawingState.frameRateForLayerTree == frameRate) {
-        return false;
-    }
-
-    mDrawingState.frameRateForLayerTree = frameRate;
-
-    // TODO(b/195668952): we probably don't need to dirty visible regions here
-    // or even store frameRateForLayerTree in mDrawingState
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    mFlinger->mScheduler
-            ->recordLayerHistory(sequence, getLayerProps(), now, now,
-                                 scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
-    return true;
-}
-
 bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps,
                                      nsecs_t now) {
     if (mDrawingState.frameRateForLayerTree == frameRate) {
@@ -1538,103 +535,10 @@
     return getDrawingState().frameRateForLayerTree;
 }
 
-bool Layer::isHiddenByPolicy() const {
-    const State& s(mDrawingState);
-    const auto& parent = mDrawingParent.promote();
-    if (parent != nullptr && parent->isHiddenByPolicy()) {
-        return true;
-    }
-    if (usingRelativeZ(LayerVector::StateSet::Drawing)) {
-        auto zOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote();
-        if (zOrderRelativeOf != nullptr) {
-            if (zOrderRelativeOf->isHiddenByPolicy()) {
-                return true;
-            }
-        }
-    }
-    if (CC_UNLIKELY(!isTransformValid())) {
-        ALOGW("Hide layer %s because it has invalid transformation.", getDebugName());
-        return true;
-    }
-    return s.flags & layer_state_t::eLayerHidden;
-}
-
-uint32_t Layer::getEffectiveUsage(uint32_t usage) const {
-    // TODO: should we do something special if mSecure is set?
-    if (mProtectedByApp) {
-        // need a hardware-protected path to external video sink
-        usage |= GraphicBuffer::USAGE_PROTECTED;
-    }
-    if (mPotentialCursor) {
-        usage |= GraphicBuffer::USAGE_CURSOR;
-    }
-    usage |= GraphicBuffer::USAGE_HW_COMPOSER;
-    return usage;
-}
-
-void Layer::skipReportingTransformHint() {
-    mSkipReportingTransformHint = true;
-}
-
-void Layer::updateTransformHint(ui::Transform::RotationFlags transformHint) {
-    if (mFlinger->mDebugDisableTransformHint || transformHint & ui::Transform::ROT_INVALID) {
-        transformHint = ui::Transform::ROT_0;
-    }
-
-    setTransformHintLegacy(transformHint);
-}
-
 // ----------------------------------------------------------------------------
 // debugging
 // ----------------------------------------------------------------------------
 
-// TODO(marissaw): add new layer state info to layer debugging
-gui::LayerDebugInfo Layer::getLayerDebugInfo(const DisplayDevice* display) const {
-    using namespace std::string_literals;
-
-    gui::LayerDebugInfo info;
-    const State& ds = getDrawingState();
-    info.mName = getName();
-    sp<Layer> parent = mDrawingParent.promote();
-    info.mParentName = parent ? parent->getName() : "none"s;
-    info.mType = getType();
-
-    info.mVisibleRegion = getVisibleRegion(display);
-    info.mSurfaceDamageRegion = surfaceDamageRegion;
-    info.mLayerStack = getLayerStack().id;
-    info.mX = ds.transform.tx();
-    info.mY = ds.transform.ty();
-    info.mZ = ds.z;
-    info.mCrop = ds.crop;
-    info.mColor = ds.color;
-    info.mFlags = ds.flags;
-    info.mPixelFormat = getPixelFormat();
-    info.mDataSpace = static_cast<android_dataspace>(getDataSpace());
-    info.mMatrix[0][0] = ds.transform[0][0];
-    info.mMatrix[0][1] = ds.transform[0][1];
-    info.mMatrix[1][0] = ds.transform[1][0];
-    info.mMatrix[1][1] = ds.transform[1][1];
-    {
-        sp<const GraphicBuffer> buffer = getBuffer();
-        if (buffer != 0) {
-            info.mActiveBufferWidth = buffer->getWidth();
-            info.mActiveBufferHeight = buffer->getHeight();
-            info.mActiveBufferStride = buffer->getStride();
-            info.mActiveBufferFormat = buffer->format;
-        } else {
-            info.mActiveBufferWidth = 0;
-            info.mActiveBufferHeight = 0;
-            info.mActiveBufferStride = 0;
-            info.mActiveBufferFormat = 0;
-        }
-    }
-    info.mNumQueuedFrames = getQueuedFrameCount();
-    info.mIsOpaque = isOpaque(ds);
-    info.mContentDirty = contentDirty;
-    info.mStretchEffect = getStretchEffect();
-    return info;
-}
-
 void Layer::miniDumpHeader(std::string& result) {
     result.append(kDumpTableRowLength, '-');
     result.append("\n");
@@ -1650,57 +554,6 @@
     result.append("\n");
 }
 
-void Layer::miniDumpLegacy(std::string& result, const DisplayDevice& display) const {
-    const auto outputLayer = findOutputLayerForDisplay(&display);
-    if (!outputLayer) {
-        return;
-    }
-
-    std::string name;
-    if (mName.length() > 77) {
-        std::string shortened;
-        shortened.append(mName, 0, 36);
-        shortened.append("[...]");
-        shortened.append(mName, mName.length() - 36);
-        name = std::move(shortened);
-    } else {
-        name = mName;
-    }
-
-    StringAppendF(&result, " %s\n", name.c_str());
-
-    const State& layerState(getDrawingState());
-    const auto& outputLayerState = outputLayer->getState();
-
-    if (layerState.zOrderRelativeOf != nullptr || mDrawingParent != nullptr) {
-        StringAppendF(&result, "  rel %6d | ", layerState.z);
-    } else {
-        StringAppendF(&result, "  %10d | ", layerState.z);
-    }
-    StringAppendF(&result, "  %10d | ", mWindowType);
-    StringAppendF(&result, "%10s | ", toString(getCompositionType(display)).c_str());
-    StringAppendF(&result, "%10s | ", toString(outputLayerState.bufferTransform).c_str());
-    const Rect& frame = outputLayerState.displayFrame;
-    StringAppendF(&result, "%4d %4d %4d %4d | ", frame.left, frame.top, frame.right, frame.bottom);
-    const FloatRect& crop = outputLayerState.sourceCrop;
-    StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
-                  crop.bottom);
-    const auto frameRate = getFrameRateForLayerTree();
-    if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
-        StringAppendF(&result, "%s %15s %17s", to_string(frameRate.vote.rate).c_str(),
-                      ftl::enum_string(frameRate.vote.type).c_str(),
-                      ftl::enum_string(frameRate.vote.seamlessness).c_str());
-    } else {
-        result.append(41, ' ');
-    }
-
-    const auto focused = isLayerFocusedBasedOnPriority(getFrameRateSelectionPriority());
-    StringAppendF(&result, "    [%s]\n", focused ? "*" : " ");
-
-    result.append(kDumpTableRowLength, '-');
-    result.append("\n");
-}
-
 void Layer::miniDump(std::string& result, const frontend::LayerSnapshot& snapshot,
                      const DisplayDevice& display) const {
     const auto outputLayer = findOutputLayerForDisplay(&display, snapshot.path);
@@ -1760,507 +613,12 @@
     mFrameTracker.getStats(outStats);
 }
 
-void Layer::dumpOffscreenDebugInfo(std::string& result) const {
-    std::string hasBuffer = hasBufferOrSidebandStream() ? " (contains buffer)" : "";
-    StringAppendF(&result, "Layer %s%s pid:%d uid:%d%s\n", getName().c_str(), hasBuffer.c_str(),
-                  mOwnerPid, mOwnerUid, isHandleAlive() ? " handleAlive" : "");
-}
-
 void Layer::onDisconnect() {
     const int32_t layerId = getSequence();
     mFlinger->mTimeStats->onDestroy(layerId);
     mFlinger->mFrameTracer->onDestroy(layerId);
 }
 
-size_t Layer::getDescendantCount() const {
-    size_t count = 0;
-    for (const sp<Layer>& child : mDrawingChildren) {
-        count += 1 + child->getChildrenCount();
-    }
-    return count;
-}
-
-void Layer::setGameModeForTree(GameMode gameMode) {
-    const auto& currentState = getDrawingState();
-    if (currentState.metadata.has(gui::METADATA_GAME_MODE)) {
-        gameMode =
-                static_cast<GameMode>(currentState.metadata.getInt32(gui::METADATA_GAME_MODE, 0));
-    }
-    setGameMode(gameMode);
-    for (const sp<Layer>& child : mCurrentChildren) {
-        child->setGameModeForTree(gameMode);
-    }
-}
-
-void Layer::addChild(const sp<Layer>& layer) {
-    mFlinger->mSomeChildrenChanged = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    mCurrentChildren.add(layer);
-    layer->setParent(sp<Layer>::fromExisting(this));
-    layer->setGameModeForTree(mGameMode);
-    updateTreeHasFrameRateVote();
-}
-
-ssize_t Layer::removeChild(const sp<Layer>& layer) {
-    mFlinger->mSomeChildrenChanged = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    layer->setParent(nullptr);
-    const auto removeResult = mCurrentChildren.remove(layer);
-
-    updateTreeHasFrameRateVote();
-    layer->setGameModeForTree(GameMode::Unsupported);
-    layer->updateTreeHasFrameRateVote();
-
-    return removeResult;
-}
-
-void Layer::setChildrenDrawingParent(const sp<Layer>& newParent) {
-    for (const sp<Layer>& child : mDrawingChildren) {
-        child->mDrawingParent = newParent;
-        const float parentShadowRadius =
-                newParent->canDrawShadows() ? 0.f : newParent->mEffectiveShadowRadius;
-        child->computeBounds(newParent->mBounds, newParent->mEffectiveTransform,
-                             parentShadowRadius);
-    }
-}
-
-bool Layer::reparent(const sp<IBinder>& newParentHandle) {
-    sp<Layer> newParent;
-    if (newParentHandle != nullptr) {
-        newParent = LayerHandle::getLayer(newParentHandle);
-        if (newParent == nullptr) {
-            ALOGE("Unable to promote Layer handle");
-            return false;
-        }
-        if (newParent == this) {
-            ALOGE("Invalid attempt to reparent Layer (%s) to itself", getName().c_str());
-            return false;
-        }
-    }
-
-    sp<Layer> parent = getParent();
-    if (parent != nullptr) {
-        parent->removeChild(sp<Layer>::fromExisting(this));
-    }
-
-    if (newParentHandle != nullptr) {
-        newParent->addChild(sp<Layer>::fromExisting(this));
-        if (!newParent->isRemovedFromCurrentState()) {
-            addToCurrentState();
-        } else {
-            onRemovedFromCurrentState();
-        }
-    } else {
-        onRemovedFromCurrentState();
-    }
-
-    return true;
-}
-
-bool Layer::setColorTransform(const mat4& matrix) {
-    static const mat4 identityMatrix = mat4();
-
-    if (mDrawingState.colorTransform == matrix) {
-        return false;
-    }
-    ++mDrawingState.sequence;
-    mDrawingState.colorTransform = matrix;
-    mDrawingState.hasColorTransform = matrix != identityMatrix;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-mat4 Layer::getColorTransform() const {
-    mat4 colorTransform = mat4(getDrawingState().colorTransform);
-    if (sp<Layer> parent = mDrawingParent.promote(); parent != nullptr) {
-        colorTransform = parent->getColorTransform() * colorTransform;
-    }
-    return colorTransform;
-}
-
-bool Layer::hasColorTransform() const {
-    bool hasColorTransform = getDrawingState().hasColorTransform;
-    if (sp<Layer> parent = mDrawingParent.promote(); parent != nullptr) {
-        hasColorTransform = hasColorTransform || parent->hasColorTransform();
-    }
-    return hasColorTransform;
-}
-
-bool Layer::isLegacyDataSpace() const {
-    // return true when no higher bits are set
-    return !(getDataSpace() &
-             (ui::Dataspace::STANDARD_MASK | ui::Dataspace::TRANSFER_MASK |
-              ui::Dataspace::RANGE_MASK));
-}
-
-void Layer::setParent(const sp<Layer>& layer) {
-    mCurrentParent = layer;
-}
-
-int32_t Layer::getZ(LayerVector::StateSet) const {
-    return mDrawingState.z;
-}
-
-bool Layer::usingRelativeZ(LayerVector::StateSet stateSet) const {
-    const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
-    const State& state = useDrawing ? mDrawingState : mDrawingState;
-    return state.isRelativeOf;
-}
-
-__attribute__((no_sanitize("unsigned-integer-overflow"))) LayerVector Layer::makeTraversalList(
-        LayerVector::StateSet stateSet, bool* outSkipRelativeZUsers) {
-    LOG_ALWAYS_FATAL_IF(stateSet == LayerVector::StateSet::Invalid,
-                        "makeTraversalList received invalid stateSet");
-    const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
-    const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
-    const State& state = useDrawing ? mDrawingState : mDrawingState;
-
-    if (state.zOrderRelatives.size() == 0) {
-        *outSkipRelativeZUsers = true;
-        return children;
-    }
-
-    LayerVector traverse(stateSet);
-    for (const wp<Layer>& weakRelative : state.zOrderRelatives) {
-        sp<Layer> strongRelative = weakRelative.promote();
-        if (strongRelative != nullptr) {
-            traverse.add(strongRelative);
-        }
-    }
-
-    for (const sp<Layer>& child : children) {
-        if (child->usingRelativeZ(stateSet)) {
-            continue;
-        }
-        traverse.add(child);
-    }
-
-    return traverse;
-}
-
-/**
- * Negatively signed relatives are before 'this' in Z-order.
- */
-void Layer::traverseInZOrder(LayerVector::StateSet stateSet, const LayerVector::Visitor& visitor) {
-    // In the case we have other layers who are using a relative Z to us, makeTraversalList will
-    // produce a new list for traversing, including our relatives, and not including our children
-    // who are relatives of another surface. In the case that there are no relative Z,
-    // makeTraversalList returns our children directly to avoid significant overhead.
-    // However in this case we need to take the responsibility for filtering children which
-    // are relatives of another surface here.
-    bool skipRelativeZUsers = false;
-    const LayerVector list = makeTraversalList(stateSet, &skipRelativeZUsers);
-
-    size_t i = 0;
-    for (; i < list.size(); i++) {
-        const auto& relative = list[i];
-        if (skipRelativeZUsers && relative->usingRelativeZ(stateSet)) {
-            continue;
-        }
-
-        if (relative->getZ(stateSet) >= 0) {
-            break;
-        }
-        relative->traverseInZOrder(stateSet, visitor);
-    }
-
-    visitor(this);
-    for (; i < list.size(); i++) {
-        const auto& relative = list[i];
-
-        if (skipRelativeZUsers && relative->usingRelativeZ(stateSet)) {
-            continue;
-        }
-        relative->traverseInZOrder(stateSet, visitor);
-    }
-}
-
-/**
- * Positively signed relatives are before 'this' in reverse Z-order.
- */
-void Layer::traverseInReverseZOrder(LayerVector::StateSet stateSet,
-                                    const LayerVector::Visitor& visitor) {
-    // See traverseInZOrder for documentation.
-    bool skipRelativeZUsers = false;
-    LayerVector list = makeTraversalList(stateSet, &skipRelativeZUsers);
-
-    int32_t i = 0;
-    for (i = int32_t(list.size()) - 1; i >= 0; i--) {
-        const auto& relative = list[i];
-
-        if (skipRelativeZUsers && relative->usingRelativeZ(stateSet)) {
-            continue;
-        }
-
-        if (relative->getZ(stateSet) < 0) {
-            break;
-        }
-        relative->traverseInReverseZOrder(stateSet, visitor);
-    }
-    visitor(this);
-    for (; i >= 0; i--) {
-        const auto& relative = list[i];
-
-        if (skipRelativeZUsers && relative->usingRelativeZ(stateSet)) {
-            continue;
-        }
-
-        relative->traverseInReverseZOrder(stateSet, visitor);
-    }
-}
-
-void Layer::traverse(LayerVector::StateSet state, const LayerVector::Visitor& visitor) {
-    visitor(this);
-    const LayerVector& children =
-          state == LayerVector::StateSet::Drawing ? mDrawingChildren : mCurrentChildren;
-    for (const sp<Layer>& child : children) {
-        child->traverse(state, visitor);
-    }
-}
-
-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,
-                        "makeTraversalList received invalid stateSet");
-    const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
-    const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
-    const State& state = useDrawing ? mDrawingState : mDrawingState;
-
-    LayerVector traverse(stateSet);
-    for (const wp<Layer>& weakRelative : state.zOrderRelatives) {
-        sp<Layer> strongRelative = weakRelative.promote();
-        // Only add relative layers that are also descendents of the top most parent of the tree.
-        // If a relative layer is not a descendent, then it should be ignored.
-        if (std::binary_search(layersInTree.begin(), layersInTree.end(), strongRelative.get())) {
-            traverse.add(strongRelative);
-        }
-    }
-
-    for (const sp<Layer>& child : children) {
-        const State& childState = useDrawing ? child->mDrawingState : child->mDrawingState;
-        // If a layer has a relativeOf layer, only ignore if the layer it's relative to is a
-        // descendent of the top most parent of the tree. If it's not a descendent, then just add
-        // the child here since it won't be added later as a relative.
-        if (std::binary_search(layersInTree.begin(), layersInTree.end(),
-                               childState.zOrderRelativeOf.promote().get())) {
-            continue;
-        }
-        traverse.add(child);
-    }
-
-    return traverse;
-}
-
-void Layer::traverseChildrenInZOrderInner(const std::vector<Layer*>& layersInTree,
-                                          LayerVector::StateSet stateSet,
-                                          const LayerVector::Visitor& visitor) {
-    const LayerVector list = makeChildrenTraversalList(stateSet, layersInTree);
-
-    size_t i = 0;
-    for (; i < list.size(); i++) {
-        const auto& relative = list[i];
-        if (relative->getZ(stateSet) >= 0) {
-            break;
-        }
-        relative->traverseChildrenInZOrderInner(layersInTree, stateSet, visitor);
-    }
-
-    visitor(this);
-    for (; i < list.size(); i++) {
-        const auto& relative = list[i];
-        relative->traverseChildrenInZOrderInner(layersInTree, stateSet, visitor);
-    }
-}
-
-std::vector<Layer*> Layer::getLayersInTree(LayerVector::StateSet stateSet) {
-    const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
-    const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
-
-    std::vector<Layer*> layersInTree = {this};
-    for (size_t i = 0; i < children.size(); i++) {
-        const auto& child = children[i];
-        std::vector<Layer*> childLayers = child->getLayersInTree(stateSet);
-        layersInTree.insert(layersInTree.end(), childLayers.cbegin(), childLayers.cend());
-    }
-
-    return layersInTree;
-}
-
-void Layer::traverseChildrenInZOrder(LayerVector::StateSet stateSet,
-                                     const LayerVector::Visitor& visitor) {
-    std::vector<Layer*> layersInTree = getLayersInTree(stateSet);
-    std::sort(layersInTree.begin(), layersInTree.end());
-    traverseChildrenInZOrderInner(layersInTree, stateSet, visitor);
-}
-
-ui::Transform Layer::getTransform() const {
-    return mEffectiveTransform;
-}
-
-bool Layer::isTransformValid() const {
-    float transformDet = getTransform().det();
-    return transformDet != 0 && !isinf(transformDet) && !isnan(transformDet);
-}
-
-half Layer::getAlpha() const {
-    const auto& p = mDrawingParent.promote();
-
-    half parentAlpha = (p != nullptr) ? p->getAlpha() : 1.0_hf;
-    return parentAlpha * getDrawingState().color.a;
-}
-
-ui::Transform::RotationFlags Layer::getFixedTransformHint() const {
-    ui::Transform::RotationFlags fixedTransformHint = mDrawingState.fixedTransformHint;
-    if (fixedTransformHint != ui::Transform::ROT_INVALID) {
-        return fixedTransformHint;
-    }
-    const auto& p = mCurrentParent.promote();
-    if (!p) return fixedTransformHint;
-    return p->getFixedTransformHint();
-}
-
-half4 Layer::getColor() const {
-    const half4 color(getDrawingState().color);
-    return half4(color.r, color.g, color.b, getAlpha());
-}
-
-int32_t Layer::getBackgroundBlurRadius() const {
-    if (getDrawingState().backgroundBlurRadius == 0) {
-        return 0;
-    }
-
-    const auto& p = mDrawingParent.promote();
-    half parentAlpha = (p != nullptr) ? p->getAlpha() : 1.0_hf;
-    return parentAlpha * getDrawingState().backgroundBlurRadius;
-}
-
-const std::vector<BlurRegion> Layer::getBlurRegions() const {
-    auto regionsCopy(getDrawingState().blurRegions);
-    float layerAlpha = getAlpha();
-    for (auto& region : regionsCopy) {
-        region.alpha = region.alpha * layerAlpha;
-    }
-    return regionsCopy;
-}
-
-RoundedCornerState Layer::getRoundedCornerState() const {
-    // Today's DPUs cannot do rounded corners. If RenderEngine cannot render
-    // protected content, remove rounded corners from protected content so it
-    // can be rendered by the DPU.
-    if (isProtected() && !mFlinger->getRenderEngine().supportsProtectedContent()) {
-        return {};
-    }
-
-    // Get parent settings
-    RoundedCornerState parentSettings;
-    const auto& parent = mDrawingParent.promote();
-    if (parent != nullptr) {
-        parentSettings = parent->getRoundedCornerState();
-        if (parentSettings.hasRoundedCorners()) {
-            ui::Transform t = getActiveTransform(getDrawingState());
-            t = t.inverse();
-            parentSettings.cropRect = t.transform(parentSettings.cropRect);
-            parentSettings.radius.x *= t.getScaleX();
-            parentSettings.radius.y *= t.getScaleY();
-        }
-    }
-
-    // Get layer settings
-    Rect layerCropRect = getCroppedBufferSize(getDrawingState());
-    const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius);
-    RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius);
-    const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid();
-
-    if (layerSettingsValid && parentSettings.hasRoundedCorners()) {
-        // If the parent and the layer have rounded corner settings, use the parent settings if the
-        // parent crop is entirely inside the layer crop.
-        // This has limitations and cause rendering artifacts. See b/200300845 for correct fix.
-        if (parentSettings.cropRect.left > layerCropRect.left &&
-            parentSettings.cropRect.top > layerCropRect.top &&
-            parentSettings.cropRect.right < layerCropRect.right &&
-            parentSettings.cropRect.bottom < layerCropRect.bottom) {
-            return parentSettings;
-        } else {
-            return layerSettings;
-        }
-    } else if (layerSettingsValid) {
-        return layerSettings;
-    } else if (parentSettings.hasRoundedCorners()) {
-        return parentSettings;
-    }
-    return {};
-}
-
-bool Layer::findInHierarchy(const sp<Layer>& l) {
-    if (l == this) {
-        return true;
-    }
-    for (auto& child : mDrawingChildren) {
-      if (child->findInHierarchy(l)) {
-          return true;
-      }
-    }
-    return false;
-}
-
-void Layer::commitChildList() {
-    for (size_t i = 0; i < mCurrentChildren.size(); i++) {
-        const auto& child = mCurrentChildren[i];
-        child->commitChildList();
-    }
-    mDrawingChildren = mCurrentChildren;
-    mDrawingParent = mCurrentParent;
-    if (CC_UNLIKELY(usingRelativeZ(LayerVector::StateSet::Drawing))) {
-        auto zOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote();
-        if (zOrderRelativeOf == nullptr) return;
-        if (findInHierarchy(zOrderRelativeOf)) {
-            ALOGE("Detected Z ordering loop between %s and %s", mName.c_str(),
-                  zOrderRelativeOf->mName.c_str());
-            ALOGE("Severing rel Z loop, potentially dangerous");
-            mDrawingState.isRelativeOf = false;
-            zOrderRelativeOf->removeZOrderRelative(wp<Layer>::fromExisting(this));
-        }
-    }
-}
-
-
-void Layer::setInputInfo(const WindowInfo& info) {
-    mDrawingState.inputInfo = info;
-    mDrawingState.touchableRegionCrop =
-            LayerHandle::getLayer(info.touchableRegionCropHandle.promote());
-    mDrawingState.modified = true;
-    mFlinger->mUpdateInputInfo = true;
-    setTransactionFlags(eTransactionNeeded);
-}
-
-perfetto::protos::LayerProto* Layer::writeToProto(perfetto::protos::LayersProto& layersProto,
-                                                  uint32_t traceFlags) {
-    perfetto::protos::LayerProto* layerProto = layersProto.add_layers();
-    writeToProtoDrawingState(layerProto);
-    writeToProtoCommonState(layerProto, LayerVector::StateSet::Drawing, traceFlags);
-
-    if (traceFlags & LayerTracing::TRACE_COMPOSITION) {
-        ui::LayerStack layerStack =
-                (mSnapshot) ? mSnapshot->outputFilter.layerStack : ui::INVALID_LAYER_STACK;
-        writeCompositionStateToProto(layerProto, layerStack);
-    }
-
-    for (const sp<Layer>& layer : mDrawingChildren) {
-        layer->writeToProto(layersProto, traceFlags);
-    }
-
-    return layerProto;
-}
-
 void Layer::writeCompositionStateToProto(perfetto::protos::LayerProto* layerProto,
                                          ui::LayerStack layerStack) {
     ftl::FakeGuard guard(mFlinger->mStateLock); // Called from the main thread.
@@ -2276,383 +634,9 @@
     }
 }
 
-void Layer::writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo) {
-    const ui::Transform transform = getTransform();
-    auto buffer = getExternalTexture();
-    if (buffer != nullptr) {
-        LayerProtoHelper::writeToProto(*buffer,
-                                       [&]() { return layerInfo->mutable_active_buffer(); });
-        LayerProtoHelper::writeToProtoDeprecated(ui::Transform(getBufferTransform()),
-                                                 layerInfo->mutable_buffer_transform());
-    }
-    layerInfo->set_invalidate(contentDirty);
-    layerInfo->set_is_protected(isProtected());
-    layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(getDataSpace())));
-    layerInfo->set_queued_frames(getQueuedFrameCount());
-    layerInfo->set_curr_frame(mCurrentFrameNumber);
-    layerInfo->set_requested_corner_radius(getDrawingState().cornerRadius);
-    layerInfo->set_corner_radius(
-            (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0);
-    layerInfo->set_background_blur_radius(getBackgroundBlurRadius());
-    layerInfo->set_is_trusted_overlay(isTrustedOverlay());
-    LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
-    LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(),
-                                           [&]() { return layerInfo->mutable_position(); });
-    LayerProtoHelper::writeToProto(mBounds, [&]() { return layerInfo->mutable_bounds(); });
-    LayerProtoHelper::writeToProto(surfaceDamageRegion,
-                                   [&]() { return layerInfo->mutable_damage_region(); });
-
-    if (hasColorTransform()) {
-        LayerProtoHelper::writeToProto(getColorTransform(), layerInfo->mutable_color_transform());
-    }
-
-    LayerProtoHelper::writeToProto(mSourceBounds,
-                                   [&]() { return layerInfo->mutable_source_bounds(); });
-    LayerProtoHelper::writeToProto(mScreenBounds,
-                                   [&]() { return layerInfo->mutable_screen_bounds(); });
-    LayerProtoHelper::writeToProto(getRoundedCornerState().cropRect,
-                                   [&]() { return layerInfo->mutable_corner_radius_crop(); });
-    layerInfo->set_shadow_radius(mEffectiveShadowRadius);
-}
-
-void Layer::writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo,
-                                    LayerVector::StateSet stateSet, uint32_t traceFlags) {
-    const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
-    const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
-    const State& state = useDrawing ? mDrawingState : mDrawingState;
-
-    ui::Transform requestedTransform = state.transform;
-
-    layerInfo->set_id(sequence);
-    layerInfo->set_name(getName().c_str());
-    layerInfo->set_type(getType());
-
-    for (const auto& child : children) {
-        layerInfo->add_children(child->sequence);
-    }
-
-    for (const wp<Layer>& weakRelative : state.zOrderRelatives) {
-        sp<Layer> strongRelative = weakRelative.promote();
-        if (strongRelative != nullptr) {
-            layerInfo->add_relatives(strongRelative->sequence);
-        }
-    }
-
-    LayerProtoHelper::writeToProto(state.transparentRegionHint,
-                                   [&]() { return layerInfo->mutable_transparent_region(); });
-
-    layerInfo->set_layer_stack(getLayerStack().id);
-    layerInfo->set_z(state.z);
-
-    LayerProtoHelper::writePositionToProto(requestedTransform.tx(), requestedTransform.ty(), [&]() {
-        return layerInfo->mutable_requested_position();
-    });
-
-    LayerProtoHelper::writeToProto(state.crop, [&]() { return layerInfo->mutable_crop(); });
-
-    layerInfo->set_is_opaque(isOpaque(state));
-
-    layerInfo->set_pixel_format(decodePixelFormat(getPixelFormat()));
-    LayerProtoHelper::writeToProto(getColor(), [&]() { return layerInfo->mutable_color(); });
-    LayerProtoHelper::writeToProto(state.color,
-                                   [&]() { return layerInfo->mutable_requested_color(); });
-    layerInfo->set_flags(state.flags);
-
-    LayerProtoHelper::writeToProtoDeprecated(requestedTransform,
-                                             layerInfo->mutable_requested_transform());
-
-    auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
-    if (parent != nullptr) {
-        layerInfo->set_parent(parent->sequence);
-    }
-
-    auto zOrderRelativeOf = state.zOrderRelativeOf.promote();
-    if (zOrderRelativeOf != nullptr) {
-        layerInfo->set_z_order_relative_of(zOrderRelativeOf->sequence);
-    }
-
-    layerInfo->set_is_relative_of(state.isRelativeOf);
-
-    layerInfo->set_owner_uid(mOwnerUid);
-
-    if ((traceFlags & LayerTracing::TRACE_INPUT) && needsInputInfo()) {
-        WindowInfo info;
-        if (useDrawing) {
-            info = fillInputInfo(
-                    InputDisplayArgs{.transform = &kIdentityTransform, .isSecure = true});
-        } else {
-            info = state.inputInfo;
-        }
-
-        LayerProtoHelper::writeToProto(info, state.touchableRegionCrop,
-                                       [&]() { return layerInfo->mutable_input_window_info(); });
-    }
-
-    if (traceFlags & LayerTracing::TRACE_EXTRA) {
-        auto protoMap = layerInfo->mutable_metadata();
-        for (const auto& entry : state.metadata.mMap) {
-            (*protoMap)[entry.first] = std::string(entry.second.cbegin(), entry.second.cend());
-        }
-    }
-
-    LayerProtoHelper::writeToProto(state.destinationFrame,
-                                   [&]() { return layerInfo->mutable_destination_frame(); });
-}
-
-bool Layer::isRemovedFromCurrentState() const  {
-    return mRemovedFromDrawingState;
-}
-
-// Applies the given transform to the region, while protecting against overflows caused by any
-// offsets. If applying the offset in the transform to any of the Rects in the region would result
-// in an overflow, they are not added to the output Region.
-static Region transformTouchableRegionSafely(const ui::Transform& t, const Region& r,
-                                             const std::string& debugWindowName) {
-    // Round the translation using the same rounding strategy used by ui::Transform.
-    const auto tx = static_cast<int32_t>(t.tx() + 0.5);
-    const auto ty = static_cast<int32_t>(t.ty() + 0.5);
-
-    ui::Transform transformWithoutOffset = t;
-    transformWithoutOffset.set(0.f, 0.f);
-
-    const Region transformed = transformWithoutOffset.transform(r);
-
-    // Apply the translation to each of the Rects in the region while discarding any that overflow.
-    Region ret;
-    for (const auto& rect : transformed) {
-        Rect newRect;
-        if (__builtin_add_overflow(rect.left, tx, &newRect.left) ||
-            __builtin_add_overflow(rect.top, ty, &newRect.top) ||
-            __builtin_add_overflow(rect.right, tx, &newRect.right) ||
-            __builtin_add_overflow(rect.bottom, ty, &newRect.bottom)) {
-            ALOGE("Applying transform to touchable region of window '%s' resulted in an overflow.",
-                  debugWindowName.c_str());
-            continue;
-        }
-        ret.orSelf(newRect);
-    }
-    return ret;
-}
-
-void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDisplay) {
-    auto [inputBounds, inputBoundsValid] = getInputBounds(/*fillParentBounds=*/false);
-    if (!inputBoundsValid) {
-        info.touchableRegion.clear();
-    }
-
-    info.frame = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
-
-    ui::Transform 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();
-
-    // The touchable region is specified in the input coordinate space. Change it to display space.
-    info.touchableRegion =
-            transformTouchableRegionSafely(inputToDisplay, info.touchableRegion, mName);
-}
-
-void Layer::fillTouchOcclusionMode(WindowInfo& info) {
-    sp<Layer> p = sp<Layer>::fromExisting(this);
-    while (p != nullptr && !p->hasInputInfo()) {
-        p = p->mDrawingParent.promote();
-    }
-    if (p != nullptr) {
-        info.touchOcclusionMode = p->mDrawingState.inputInfo.touchOcclusionMode;
-    }
-}
-
-gui::DropInputMode Layer::getDropInputMode() const {
-    gui::DropInputMode mode = mDrawingState.dropInputMode;
-    if (mode == gui::DropInputMode::ALL) {
-        return mode;
-    }
-    sp<Layer> parent = mDrawingParent.promote();
-    if (parent) {
-        gui::DropInputMode parentMode = parent->getDropInputMode();
-        if (parentMode != gui::DropInputMode::NONE) {
-            return parentMode;
-        }
-    }
-    return mode;
-}
-
-void Layer::handleDropInputMode(gui::WindowInfo& info) const {
-    if (mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
-        return;
-    }
-
-    // Check if we need to drop input unconditionally
-    gui::DropInputMode dropInputMode = getDropInputMode();
-    if (dropInputMode == gui::DropInputMode::ALL) {
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
-        ALOGV("Dropping input for %s as requested by policy.", getDebugName());
-        return;
-    }
-
-    // Check if we need to check if the window is obscured by parent
-    if (dropInputMode != gui::DropInputMode::OBSCURED) {
-        return;
-    }
-
-    // Check if the parent has set an alpha on the layer
-    sp<Layer> parent = mDrawingParent.promote();
-    if (parent && parent->getAlpha() != 1.0_hf) {
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
-        ALOGV("Dropping input for %s as requested by policy because alpha=%f", getDebugName(),
-              static_cast<float>(getAlpha()));
-    }
-
-    // Check if the parent has cropped the buffer
-    Rect bufferSize = getCroppedBufferSize(getDrawingState());
-    if (!bufferSize.isValid()) {
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
-        return;
-    }
-
-    // Screenbounds are the layer bounds cropped by parents, transformed to screenspace.
-    // To check if the layer has been cropped, we take the buffer bounds, apply the local
-    // layer crop and apply the same set of transforms to move to screenspace. If the bounds
-    // match then the layer has not been cropped by its parents.
-    Rect bufferInScreenSpace(getTransform().transform(bufferSize));
-    bool croppedByParent = bufferInScreenSpace != Rect{mScreenBounds};
-
-    if (croppedByParent) {
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
-        ALOGV("Dropping input for %s as requested by policy because buffer is cropped by parent",
-              getDebugName());
-    } else {
-        // If the layer is not obscured by its parents (by setting an alpha or crop), then only drop
-        // input if the window is obscured. This check should be done in surfaceflinger but the
-        // logic currently resides in inputflinger. So pass the if_obscured check to input to only
-        // drop input events if the window is obscured.
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
-    }
-}
-
-WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
-    if (!hasInputInfo()) {
-        mDrawingState.inputInfo.name = getName();
-        mDrawingState.inputInfo.ownerUid = gui::Uid{mOwnerUid};
-        mDrawingState.inputInfo.ownerPid = gui::Pid{mOwnerPid};
-        mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL;
-        mDrawingState.inputInfo.displayId = getLayerStack().id;
-    }
-
-    const ui::Transform& displayTransform =
-            displayArgs.transform != nullptr ? *displayArgs.transform : kIdentityTransform;
-
-    WindowInfo info = mDrawingState.inputInfo;
-    info.id = sequence;
-    info.displayId = getLayerStack().id;
-
-    fillInputFrameInfo(info, displayTransform);
-
-    if (displayArgs.transform == nullptr) {
-        // Do not let the window receive touches if it is not associated with a valid display
-        // transform. We still allow the window to receive keys and prevent ANRs.
-        info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE;
-    }
-
-    info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !isVisibleForInput());
-
-    info.alpha = getAlpha();
-    fillTouchOcclusionMode(info);
-    handleDropInputMode(info);
-
-    // If the window will be blacked out on a display because the display does not have the secure
-    // flag and the layer has the secure flag set, then drop input.
-    if (!displayArgs.isSecure && isSecure()) {
-        info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
-    }
-
-    sp<Layer> cropLayer = mDrawingState.touchableRegionCrop.promote();
-    if (info.replaceTouchableRegionWithCrop) {
-        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) {
-        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
-    // if it was set by WM for a known system overlay
-    if (isTrustedOverlay()) {
-        info.inputConfig |= WindowInfo::InputConfig::TRUSTED_OVERLAY;
-    }
-
-    // If the layer is a clone, we need to crop the input region to cloned root to prevent
-    // touches from going outside the cloned area.
-    if (isClone()) {
-        info.inputConfig |= WindowInfo::InputConfig::CLONE;
-        if (const sp<Layer> clonedRoot = getClonedRoot()) {
-            const Rect rect = displayTransform.transform(Rect{clonedRoot->mScreenBounds});
-            info.touchableRegion = info.touchableRegion.intersect(rect);
-        }
-    }
-
-    Rect bufferSize = getBufferSize(getDrawingState());
-    info.contentSize = Size(bufferSize.width(), bufferSize.height());
-
-    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);
-    }
-    if (mDrawingParent == nullptr || mDrawingParent.promote() == nullptr) {
-        return nullptr;
-    }
-    return mDrawingParent.promote()->getClonedRoot();
-}
-
-bool Layer::hasInputInfo() const {
-    return mDrawingState.inputInfo.token != nullptr ||
-            mDrawingState.inputInfo.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
-}
-
 compositionengine::OutputLayer* Layer::findOutputLayerForDisplay(
         const DisplayDevice* display) const {
     if (!display) return nullptr;
-    if (!mFlinger->mLayerLifecycleManagerEnabled) {
-        return display->getCompositionDisplay()->getOutputLayerForLayer(
-                getCompositionEngineLayerFE());
-    }
     sp<LayerFE> layerFE;
     frontend::LayerHierarchy::TraversalPath path{.id = static_cast<uint32_t>(sequence)};
     for (auto& [p, layer] : mLayerFEs) {
@@ -2668,10 +652,6 @@
 compositionengine::OutputLayer* Layer::findOutputLayerForDisplay(
         const DisplayDevice* display, const frontend::LayerHierarchy::TraversalPath& path) const {
     if (!display) return nullptr;
-    if (!mFlinger->mLayerLifecycleManagerEnabled) {
-        return display->getCompositionDisplay()->getOutputLayerForLayer(
-                getCompositionEngineLayerFE());
-    }
     sp<LayerFE> layerFE;
     for (auto& [p, layer] : mLayerFEs) {
         if (p == path) {
@@ -2688,233 +668,30 @@
     return outputLayer ? outputLayer->getState().visibleRegion : Region();
 }
 
-void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
-    if (mFlinger->mLayerLifecycleManagerEnabled) return;
-    mSnapshot->path.id = clonedFrom->getSequence();
-    mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
-
-    cloneDrawingState(clonedFrom.get());
-    mClonedFrom = clonedFrom;
-    mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha;
-    mPotentialCursor = clonedFrom->mPotentialCursor;
-    mProtectedByApp = clonedFrom->mProtectedByApp;
-    updateCloneBufferInfo();
-}
-
-void Layer::updateCloneBufferInfo() {
-    if (!isClone() || !isClonedFromAlive()) {
-        return;
-    }
-
-    sp<Layer> clonedFrom = getClonedFrom();
-    mBufferInfo = clonedFrom->mBufferInfo;
-    mSidebandStream = clonedFrom->mSidebandStream;
-    surfaceDamageRegion = clonedFrom->surfaceDamageRegion;
-    mCurrentFrameNumber = clonedFrom->mCurrentFrameNumber.load();
-    mPreviousFrameNumber = clonedFrom->mPreviousFrameNumber;
-
-    // After buffer info is updated, the drawingState from the real layer needs to be copied into
-    // the cloned. This is because some properties of drawingState can change when latchBuffer is
-    // called. However, copying the drawingState would also overwrite the cloned layer's relatives
-    // and touchableRegionCrop. Therefore, temporarily store the relatives so they can be set in
-    // the cloned drawingState again.
-    wp<Layer> tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf;
-    SortedVector<wp<Layer>> tmpZOrderRelatives = mDrawingState.zOrderRelatives;
-    wp<Layer> tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop;
-    WindowInfo tmpInputInfo = mDrawingState.inputInfo;
-
-    cloneDrawingState(clonedFrom.get());
-
-    mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop;
-    mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf;
-    mDrawingState.zOrderRelatives = tmpZOrderRelatives;
-    mDrawingState.inputInfo = tmpInputInfo;
-}
-
-bool Layer::updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates) {
-    if (mClonedChild == nullptr || !mClonedChild->isClonedFromAlive()) {
-        // If mClonedChild is null, there is nothing to mirror. If isClonedFromAlive returns false,
-        // it means that there is a clone, but the layer it was cloned from has been destroyed. In
-        // that case, we want to delete the reference to the clone since we want it to get
-        // destroyed. The root, this layer, will still be around since the client can continue
-        // to hold a reference, but no cloned layers will be displayed.
-        mClonedChild = nullptr;
-        return true;
-    }
-
-    std::map<sp<Layer>, sp<Layer>> clonedLayersMap;
-    // If the real layer exists and is in current state, add the clone as a child of the root.
-    // There's no need to remove from drawingState when the layer is offscreen since currentState is
-    // copied to drawingState for the root layer. So the clonedChild is always removed from
-    // drawingState and then needs to be added back each traversal.
-    if (!mClonedChild->getClonedFrom()->isRemovedFromCurrentState()) {
-        addChildToDrawing(mClonedChild);
-    }
-
-    mClonedChild->updateClonedDrawingState(clonedLayersMap);
-    mClonedChild->updateClonedChildren(sp<Layer>::fromExisting(this), clonedLayersMap);
-    mClonedChild->updateClonedRelatives(clonedLayersMap);
-
-    for (Layer* root : cloneRootsPendingUpdates) {
-        if (clonedLayersMap.find(sp<Layer>::fromExisting(root)) != clonedLayersMap.end()) {
-            return false;
-        }
-    }
-    return true;
-}
-
-void Layer::updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
-    // If the layer the clone was cloned from is alive, copy the content of the drawingState
-    // to the clone. If the real layer is no longer alive, continue traversing the children
-    // since we may be able to pull out other children that are still alive.
-    if (isClonedFromAlive()) {
-        sp<Layer> clonedFrom = getClonedFrom();
-        cloneDrawingState(clonedFrom.get());
-        clonedLayersMap.emplace(clonedFrom, sp<Layer>::fromExisting(this));
-    }
-
-    // The clone layer may have children in drawingState since they may have been created and
-    // added from a previous request to updateMirorInfo. This is to ensure we don't recreate clones
-    // that already exist, since we can just re-use them.
-    // The drawingChildren will not get overwritten by the currentChildren since the clones are
-    // not updated in the regular traversal. They are skipped since the root will lose the
-    // reference to them when it copies its currentChildren to drawing.
-    for (sp<Layer>& child : mDrawingChildren) {
-        child->updateClonedDrawingState(clonedLayersMap);
-    }
-}
-
-void Layer::updateClonedChildren(const sp<Layer>& mirrorRoot,
-                                 std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
-    mDrawingChildren.clear();
-
-    if (!isClonedFromAlive()) {
-        return;
-    }
-
-    sp<Layer> clonedFrom = getClonedFrom();
-    for (sp<Layer>& child : clonedFrom->mDrawingChildren) {
-        if (child == mirrorRoot) {
-            // This is to avoid cyclical mirroring.
-            continue;
-        }
-        sp<Layer> clonedChild = clonedLayersMap[child];
-        if (clonedChild == nullptr) {
-            clonedChild = child->createClone(mirrorRoot->getSequence());
-            clonedLayersMap[child] = clonedChild;
-        }
-        addChildToDrawing(clonedChild);
-        clonedChild->updateClonedChildren(mirrorRoot, clonedLayersMap);
-    }
-}
-
-void Layer::updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
-    auto cropLayer = mDrawingState.touchableRegionCrop.promote();
-    if (cropLayer != nullptr) {
-        if (clonedLayersMap.count(cropLayer) == 0) {
-            // Real layer had a crop layer but it's not in the cloned hierarchy. Just set to
-            // self as crop layer to avoid going outside bounds.
-            mDrawingState.touchableRegionCrop = wp<Layer>::fromExisting(this);
-        } else {
-            const sp<Layer>& clonedCropLayer = clonedLayersMap.at(cropLayer);
-            mDrawingState.touchableRegionCrop = clonedCropLayer;
-        }
-    }
-    // Cloned layers shouldn't handle watch outside since their z order is not determined by
-    // WM or the client.
-    mDrawingState.inputInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, false);
-}
-
-void Layer::updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
-    mDrawingState.zOrderRelativeOf = wp<Layer>();
-    mDrawingState.zOrderRelatives.clear();
-
-    if (!isClonedFromAlive()) {
-        return;
-    }
-
-    const sp<Layer>& clonedFrom = getClonedFrom();
-    for (wp<Layer>& relativeWeak : clonedFrom->mDrawingState.zOrderRelatives) {
-        const sp<Layer>& relative = relativeWeak.promote();
-        if (clonedLayersMap.count(relative) > 0) {
-            auto& clonedRelative = clonedLayersMap.at(relative);
-            mDrawingState.zOrderRelatives.add(clonedRelative);
-        }
-    }
-
-    // Check if the relativeLayer for the real layer is part of the cloned hierarchy.
-    // It's possible that the layer it's relative to is outside the requested cloned hierarchy.
-    // In that case, we treat the layer as if the relativeOf has been removed. This way, it will
-    // still traverse the children, but the layer with the missing relativeOf will not be shown
-    // on screen.
-    const sp<Layer>& relativeOf = clonedFrom->mDrawingState.zOrderRelativeOf.promote();
-    if (clonedLayersMap.count(relativeOf) > 0) {
-        const sp<Layer>& clonedRelativeOf = clonedLayersMap.at(relativeOf);
-        mDrawingState.zOrderRelativeOf = clonedRelativeOf;
-    }
-
-    updateClonedInputInfo(clonedLayersMap);
-
-    for (sp<Layer>& child : mDrawingChildren) {
-        child->updateClonedRelatives(clonedLayersMap);
-    }
-}
-
-void Layer::addChildToDrawing(const sp<Layer>& layer) {
-    mDrawingChildren.add(layer);
-    layer->mDrawingParent = sp<Layer>::fromExisting(this);
-}
-
-bool Layer::isInternalDisplayOverlay() const {
-    const State& s(mDrawingState);
-    if (s.flags & layer_state_t::eLayerSkipScreenshot) {
-        return true;
-    }
-
-    sp<Layer> parent = mDrawingParent.promote();
-    return parent && parent->isInternalDisplayOverlay();
-}
-
-void Layer::setClonedChild(const sp<Layer>& clonedChild) {
-    mClonedChild = clonedChild;
-    mHadClonedChild = true;
-    mFlinger->mLayerMirrorRoots.push_back(this);
-}
-
-bool Layer::setDropInputMode(gui::DropInputMode mode) {
-    if (mDrawingState.dropInputMode == mode) {
-        return false;
-    }
-    mDrawingState.dropInputMode = mode;
-    return true;
-}
-
-void Layer::cloneDrawingState(const Layer* from) {
-    mDrawingState = from->mDrawingState;
-    // Skip callback info since they are not applicable for cloned layers.
-    mDrawingState.releaseBufferListener = nullptr;
-    // TODO (b/238781169) currently broken for mirror layers because we do not
-    // track release fences for mirror layers composed on other displays
-    mDrawingState.callbackHandles = {};
-}
-
 void Layer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                       const sp<GraphicBuffer>& buffer, uint64_t framenumber,
                                       const sp<Fence>& releaseFence) {
-    if (!listener) {
+    if (!listener && !mBufferReleaseChannel) {
         return;
     }
-    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+
+    SFTRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
+
+    ReleaseCallbackId callbackId{buffer->getId(), framenumber};
+    const sp<Fence>& fence = releaseFence ? releaseFence : Fence::NO_FENCE;
     uint32_t currentMaxAcquiredBufferCount =
             mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
-    listener->onReleaseBuffer({buffer->getId(), framenumber},
-                              releaseFence ? releaseFence : Fence::NO_FENCE,
-                              currentMaxAcquiredBufferCount);
+
+    if (listener) {
+        listener->onReleaseBuffer(callbackId, fence, currentMaxAcquiredBufferCount);
+    }
+
+    if (mBufferReleaseChannel) {
+        mBufferReleaseChannel->writeReleaseFence(callbackId, fence, currentMaxAcquiredBufferCount);
+    }
 }
 
-void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
-                             ui::LayerStack layerStack,
-                             std::function<FenceResult(FenceResult)>&& continuation) {
+sp<CallbackHandle> Layer::findCallbackHandle() {
     // If we are displayed on multiple displays in a single composition cycle then we would
     // need to do careful tracking to enable the use of the mLastClientCompositionFence.
     //  For example we can only use it if all the displays are client comp, and we need
@@ -2949,39 +726,25 @@
             break;
         }
     }
+    return ch;
+}
 
-    if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
-        futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
-    }
+void Layer::prepareReleaseCallbacks(ftl::Future<FenceResult> futureFenceResult,
+                                    ui::LayerStack layerStack) {
+    sp<CallbackHandle> ch = findCallbackHandle();
 
     if (ch != nullptr) {
         ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
         ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
         ch->name = mName;
-    } else if (FlagManager::getInstance().screenshot_fence_preservation()) {
-        // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
-        // asynchronously, then make sure we don't drop the fence.
-        mAdditionalPreviousReleaseFences.emplace_back(std::move(futureFenceResult),
-                                                      std::move(continuation));
-        std::vector<FenceAndContinuation> mergedFences;
-        sp<Fence> prevFence = nullptr;
-        // For a layer that's frequently screenshotted, try to merge fences to make sure we don't
-        // grow unbounded.
-        for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
-            auto result = futureAndContinution.future.wait_for(0s);
-            if (result != std::future_status::ready) {
-                mergedFences.emplace_back(futureAndContinution);
-                continue;
-            }
-
-            mergeFence(getDebugName(), futureAndContinution.chain().get().value_or(Fence::NO_FENCE),
-                       prevFence);
-        }
-        if (prevFence != nullptr) {
-            mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
-        }
-
-        mAdditionalPreviousReleaseFences.swap(mergedFences);
+    } else {
+        // If we didn't get a release callback yet (e.g. some scenarios when capturing
+        // screenshots asynchronously) then make sure we don't drop the fence.
+        // Older fences for the same layer stack can be dropped when a new fence arrives.
+        // An assumption here is that RenderEngine performs work sequentially, so an
+        // incoming fence will not fire before an existing fence.
+        mAdditionalPreviousReleaseFences.emplace_or_replace(layerStack,
+                                                            std::move(futureFenceResult));
     }
 
     if (mBufferInfo.mBuffer) {
@@ -2993,39 +756,63 @@
     }
 }
 
-void Layer::onSurfaceFrameCreated(
-        const std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
-    while (mPendingJankClassifications.size() >= kPendingClassificationMaxSurfaceFrames) {
-        // Too many SurfaceFrames pending classification. The front of the deque is probably not
-        // tracked by FrameTimeline and will never be presented. This will only result in a memory
-        // leak.
-        if (hasBufferOrSidebandStreamInDrawing()) {
-            // Only log for layers with a buffer, since we expect the jank data to be drained for
-            // these, while there may be no jank listeners for bufferless layers.
-            ALOGW("Removing the front of pending jank deque from layer - %s to prevent memory leak",
-                  mName.c_str());
-            std::string miniDump = mPendingJankClassifications.front()->miniDump();
-            ALOGD("Head SurfaceFrame mini dump\n%s", miniDump.c_str());
-        }
-        mPendingJankClassifications.pop_front();
+void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult,
+                             ui::LayerStack layerStack,
+                             std::function<FenceResult(FenceResult)>&& continuation) {
+    sp<CallbackHandle> ch = findCallbackHandle();
+
+    if (!FlagManager::getInstance().screenshot_fence_preservation() && continuation) {
+        futureFenceResult = ftl::Future(futureFenceResult).then(std::move(continuation)).share();
     }
-    mPendingJankClassifications.emplace_back(surfaceFrame);
+
+    if (ch != nullptr) {
+        ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+        ch->previousSharedReleaseFences.emplace_back(std::move(futureFenceResult));
+        ch->name = mName;
+    } else if (FlagManager::getInstance().screenshot_fence_preservation()) {
+        // If we didn't get a release callback yet, e.g. some scenarios when capturing screenshots
+        // asynchronously, then make sure we don't drop the fence.
+        mPreviousReleaseFenceAndContinuations.emplace_back(std::move(futureFenceResult),
+                                                           std::move(continuation));
+        std::vector<FenceAndContinuation> mergedFences;
+        sp<Fence> prevFence = nullptr;
+        // For a layer that's frequently screenshotted, try to merge fences to make sure we don't
+        // grow unbounded.
+        for (const auto& futureAndContinuation : mPreviousReleaseFenceAndContinuations) {
+            auto result = futureAndContinuation.future.wait_for(0s);
+            if (result != std::future_status::ready) {
+                mergedFences.emplace_back(futureAndContinuation);
+                continue;
+            }
+
+            mergeFence(getDebugName(),
+                       futureAndContinuation.chain().get().value_or(Fence::NO_FENCE), prevFence);
+        }
+        if (prevFence != nullptr) {
+            mergedFences.emplace_back(ftl::yield(FenceResult(std::move(prevFence))).share());
+        }
+
+        mPreviousReleaseFenceAndContinuations.swap(mergedFences);
+    }
+
+    if (mBufferInfo.mBuffer) {
+        mPreviouslyPresentedLayerStacks.push_back(layerStack);
+    }
+
+    if (mDrawingState.frameNumber > 0) {
+        mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+    }
 }
 
 void Layer::releasePendingBuffer(nsecs_t dequeueReadyTime) {
     for (const auto& handle : mDrawingState.callbackHandles) {
-        if (mFlinger->mLayerLifecycleManagerEnabled) {
-            handle->transformHint = mTransformHint;
-        } else {
-            handle->transformHint = mSkipReportingTransformHint
-                    ? std::nullopt
-                    : std::make_optional<uint32_t>(mTransformHintLegacy);
-        }
+        handle->bufferReleaseChannel = mBufferReleaseChannel;
+        handle->transformHint = mTransformHint;
         handle->dequeueReadyTime = dequeueReadyTime;
         handle->currentMaxAcquiredBufferCount =
                 mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mOwnerUid);
-        ATRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(),
-                              handle->previousReleaseCallbackId.framenumber);
+        SFTRACE_FORMAT_INSTANT("releasePendingBuffer %s - %" PRIu64, getDebugName(),
+                               handle->previousReleaseCallbackId.framenumber);
     }
 
     for (auto& handle : mDrawingState.callbackHandles) {
@@ -3035,24 +822,13 @@
         }
     }
 
-    std::vector<JankData> jankData;
-    transferAvailableJankData(mDrawingState.callbackHandles, jankData);
-    mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles,
-                                                                 jankData);
+    mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles);
     mDrawingState.callbackHandles = {};
 }
 
-bool Layer::willPresentCurrentTransaction() const {
-    // Returns true if the most recent Transaction applied to CurrentState will be presented.
-    return (getSidebandStreamChanged() || getAutoRefresh() ||
-            (mDrawingState.modified &&
-             (mDrawingState.buffer != nullptr || mDrawingState.bgColorLayer != nullptr)));
-}
-
 bool Layer::setTransform(uint32_t transform) {
     if (mDrawingState.bufferTransform == transform) return false;
     mDrawingState.bufferTransform = transform;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
@@ -3061,111 +837,10 @@
     if (mDrawingState.transformToDisplayInverse == transformToDisplayInverse) return false;
     mDrawingState.sequence++;
     mDrawingState.transformToDisplayInverse = transformToDisplayInverse;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
 
-bool Layer::setBufferCrop(const Rect& bufferCrop) {
-    if (mDrawingState.bufferCrop == bufferCrop) return false;
-
-    mDrawingState.sequence++;
-    mDrawingState.bufferCrop = bufferCrop;
-
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setDestinationFrame(const Rect& destinationFrame) {
-    if (mDrawingState.destinationFrame == destinationFrame) return false;
-
-    mDrawingState.sequence++;
-    mDrawingState.destinationFrame = destinationFrame;
-
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-// Translate destination frame into scale and position. If a destination frame is not set, use the
-// provided scale and position
-bool Layer::updateGeometry() {
-    if ((mDrawingState.flags & layer_state_t::eIgnoreDestinationFrame) ||
-        mDrawingState.destinationFrame.isEmpty()) {
-        // If destination frame is not set, use the requested transform set via
-        // Layer::setPosition and Layer::setMatrix.
-        return assignTransform(&mDrawingState.transform, mRequestedTransform);
-    }
-
-    Rect destRect = mDrawingState.destinationFrame;
-    int32_t destW = destRect.width();
-    int32_t destH = destRect.height();
-    if (destRect.left < 0) {
-        destRect.left = 0;
-        destRect.right = destW;
-    }
-    if (destRect.top < 0) {
-        destRect.top = 0;
-        destRect.bottom = destH;
-    }
-
-    if (!mDrawingState.buffer) {
-        ui::Transform t;
-        t.set(destRect.left, destRect.top);
-        return assignTransform(&mDrawingState.transform, t);
-    }
-
-    uint32_t bufferWidth = mDrawingState.buffer->getWidth();
-    uint32_t bufferHeight = mDrawingState.buffer->getHeight();
-    // Undo any transformations on the buffer.
-    if (mDrawingState.bufferTransform & ui::Transform::ROT_90) {
-        std::swap(bufferWidth, bufferHeight);
-    }
-    uint32_t invTransform = SurfaceFlinger::getActiveDisplayRotationFlags();
-    if (mDrawingState.transformToDisplayInverse) {
-        if (invTransform & ui::Transform::ROT_90) {
-            std::swap(bufferWidth, bufferHeight);
-        }
-    }
-
-    float sx = destW / static_cast<float>(bufferWidth);
-    float sy = destH / static_cast<float>(bufferHeight);
-    ui::Transform t;
-    t.set(sx, 0, 0, sy);
-    t.set(destRect.left, destRect.top);
-    return assignTransform(&mDrawingState.transform, t);
-}
-
-bool Layer::setMatrix(const layer_state_t::matrix22_t& matrix) {
-    if (mRequestedTransform.dsdx() == matrix.dsdx && mRequestedTransform.dtdy() == matrix.dtdy &&
-        mRequestedTransform.dtdx() == matrix.dtdx && mRequestedTransform.dsdy() == matrix.dsdy) {
-        return false;
-    }
-
-    mRequestedTransform.set(matrix.dsdx, matrix.dtdy, matrix.dtdx, matrix.dsdy);
-
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    return true;
-}
-
-bool Layer::setPosition(float x, float y) {
-    if (mRequestedTransform.tx() == x && mRequestedTransform.ty() == y) {
-        return false;
-    }
-
-    mRequestedTransform.set(x, y);
-
-    mDrawingState.sequence++;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-
-    return true;
-}
-
 void Layer::releasePreviousBuffer() {
     mReleasePreviousBuffer = true;
     if (!mBufferInfo.mBuffer ||
@@ -3208,15 +883,14 @@
 
 bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
                       const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
-                      bool isAutoTimestamp, std::optional<nsecs_t> dequeueTime,
-                      const FrameTimelineInfo& info) {
-    ATRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
+                      bool isAutoTimestamp, const FrameTimelineInfo& info, gui::GameMode gameMode) {
+    SFTRACE_FORMAT("setBuffer %s - hasBuffer=%s", getDebugName(), (buffer ? "true" : "false"));
 
     const bool frameNumberChanged =
             bufferData.flags.test(BufferData::BufferDataChange::frameNumberChanged);
     const uint64_t frameNumber =
             frameNumberChanged ? bufferData.frameNumber : mDrawingState.frameNumber + 1;
-    ATRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
+    SFTRACE_FORMAT_INSTANT("setBuffer %s - %" PRIu64, getDebugName(), frameNumber);
 
     if (mDrawingState.buffer) {
         releasePreviousBuffer();
@@ -3230,17 +904,16 @@
     mDrawingState.isAutoTimestamp = isAutoTimestamp;
     mDrawingState.latchedVsyncId = info.vsyncId;
     mDrawingState.useVsyncIdForRefreshRateSelection = info.useForRefreshRateSelection;
-    mDrawingState.modified = true;
     if (!buffer) {
         resetDrawingStateBufferInfo();
         setTransactionFlags(eTransactionNeeded);
         mDrawingState.bufferSurfaceFrameTX = nullptr;
-        setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+        setFrameTimelineVsyncForBufferlessTransaction(info, postTime, gameMode);
         return true;
     } else {
         // release sideband stream if it exists and a non null buffer is being set
         if (mDrawingState.sidebandStream != nullptr) {
-            setSidebandStream(nullptr, info, postTime);
+            setSidebandStream(nullptr, info, postTime, gameMode);
         }
     }
 
@@ -3279,18 +952,15 @@
 
     const int32_t layerId = getSequence();
     mFlinger->mTimeStats->setPostTime(layerId, mDrawingState.frameNumber, getName().c_str(),
-                                      mOwnerUid, postTime, getGameMode());
+                                      mOwnerUid, postTime, gameMode);
 
-    if (mFlinger->mLegacyFrontEndEnabled) {
-        recordLayerHistoryBufferUpdate(getLayerProps(), systemTime());
-    }
+    setFrameTimelineVsyncForBufferTransaction(info, postTime, gameMode);
 
-    setFrameTimelineVsyncForBufferTransaction(info, postTime);
-
-    if (dequeueTime && *dequeueTime != 0) {
+    if (bufferData.dequeueTime > 0) {
         const uint64_t bufferId = mDrawingState.buffer->getId();
         mFlinger->mFrameTracer->traceNewLayer(layerId, getName().c_str());
-        mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, *dequeueTime,
+        mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber,
+                                               bufferData.dequeueTime,
                                                FrameTracer::FrameEvent::DEQUEUE);
         mFlinger->mFrameTracer->traceTimestamp(layerId, bufferId, frameNumber, postTime,
                                                FrameTracer::FrameEvent::QUEUE);
@@ -3314,10 +984,10 @@
 }
 
 void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps, nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const nsecs_t presentTime = [&] {
         if (!mDrawingState.isAutoTimestamp) {
-            ATRACE_FORMAT_INSTANT("desiredPresentTime");
+            SFTRACE_FORMAT_INSTANT("desiredPresentTime");
             return mDrawingState.desiredPresentTime;
         }
 
@@ -3326,7 +996,7 @@
                     mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
                             mDrawingState.latchedVsyncId);
             if (prediction.has_value()) {
-                ATRACE_FORMAT_INSTANT("predictedPresentTime");
+                SFTRACE_FORMAT_INSTANT("predictedPresentTime");
                 mMaxTimeForUseVsyncId = prediction->presentTime +
                         scheduler::LayerHistory::kMaxPeriodForHistory.count();
                 return prediction->presentTime;
@@ -3362,9 +1032,9 @@
         return static_cast<nsecs_t>(0);
     }();
 
-    if (ATRACE_ENABLED() && presentTime > 0) {
+    if (SFTRACE_ENABLED() && presentTime > 0) {
         const auto presentIn = TimePoint::fromNs(presentTime) - TimePoint::now();
-        ATRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
+        SFTRACE_FORMAT_INSTANT("presentIn %s", to_string(presentIn).c_str());
     }
 
     mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, now,
@@ -3381,7 +1051,6 @@
 bool Layer::setDataspace(ui::Dataspace dataspace) {
     if (mDrawingState.dataspace == dataspace) return false;
     mDrawingState.dataspace = dataspace;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
@@ -3392,7 +1061,6 @@
         return false;
     mDrawingState.currentHdrSdrRatio = currentBufferRatio;
     mDrawingState.desiredHdrSdrRatio = desiredRatio;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
@@ -3400,46 +1068,12 @@
 bool Layer::setDesiredHdrHeadroom(float desiredRatio) {
     if (mDrawingState.desiredHdrSdrRatio == desiredRatio) return false;
     mDrawingState.desiredHdrSdrRatio = desiredRatio;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setCachingHint(gui::CachingHint cachingHint) {
-    if (mDrawingState.cachingHint == cachingHint) return false;
-    mDrawingState.cachingHint = cachingHint;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setHdrMetadata(const HdrMetadata& hdrMetadata) {
-    if (mDrawingState.hdrMetadata == hdrMetadata) return false;
-    mDrawingState.hdrMetadata = hdrMetadata;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::setSurfaceDamageRegion(const Region& surfaceDamage) {
-    if (mDrawingState.surfaceDamageRegion.hasSameRects(surfaceDamage)) return false;
-    mDrawingState.surfaceDamageRegion = surfaceDamage;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    setIsSmallDirty(surfaceDamage, getTransform());
-    return true;
-}
-
-bool Layer::setApi(int32_t api) {
-    if (mDrawingState.api == api) return false;
-    mDrawingState.api = api;
-    mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
     return true;
 }
 
 bool Layer::setSidebandStream(const sp<NativeHandle>& sidebandStream, const FrameTimelineInfo& info,
-                              nsecs_t postTime) {
+                              nsecs_t postTime, gui::GameMode gameMode) {
     if (mDrawingState.sidebandStream == sidebandStream) return false;
 
     if (mDrawingState.sidebandStream != nullptr && sidebandStream == nullptr) {
@@ -3449,12 +1083,11 @@
     }
 
     mDrawingState.sidebandStream = sidebandStream;
-    mDrawingState.modified = true;
     if (sidebandStream != nullptr && mDrawingState.buffer != nullptr) {
         releasePreviousBuffer();
         resetDrawingStateBufferInfo();
         mDrawingState.bufferSurfaceFrameTX = nullptr;
-        setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+        setFrameTimelineVsyncForBufferlessTransaction(info, postTime, gameMode);
     }
     setTransactionFlags(eTransactionNeeded);
     if (!mSidebandStreamChanged.exchange(true)) {
@@ -3483,16 +1116,23 @@
             handle->acquireTimeOrFence = mCallbackHandleAcquireTimeOrFence;
             handle->frameNumber = mDrawingState.frameNumber;
             handle->previousFrameNumber = mDrawingState.previousFrameNumber;
-            if (FlagManager::getInstance().screenshot_fence_preservation() &&
+            if (FlagManager::getInstance().ce_fence_promise() &&
                 mPreviousReleaseBufferEndpoint == handle->listener) {
-                // Add fences from previous screenshots now so that they can be dispatched to the
+                // Add fence from previous screenshot now so that it can be dispatched to the
                 // client.
-                for (const auto& futureAndContinution : mAdditionalPreviousReleaseFences) {
-                    handle->previousReleaseFences.emplace_back(futureAndContinution.chain());
+                for (auto& [_, future] : mAdditionalPreviousReleaseFences) {
+                    handle->previousReleaseFences.emplace_back(std::move(future));
                 }
                 mAdditionalPreviousReleaseFences.clear();
+            } else if (FlagManager::getInstance().screenshot_fence_preservation() &&
+                       mPreviousReleaseBufferEndpoint == handle->listener) {
+                // Add fences from previous screenshots now so that they can be dispatched to the
+                // client.
+                for (const auto& futureAndContinution : mPreviousReleaseFenceAndContinuations) {
+                    handle->previousSharedReleaseFences.emplace_back(futureAndContinution.chain());
+                }
+                mPreviousReleaseFenceAndContinuations.clear();
             }
-
             // Store so latched time and release fence can be set
             mDrawingState.callbackHandles.push_back(handle);
 
@@ -3505,9 +1145,7 @@
     if (!remainingHandles.empty()) {
         // Notify the transaction completed threads these handles are done. These are only the
         // handles that were not added to the mDrawingState, which will be notified later.
-        std::vector<JankData> jankData;
-        transferAvailableJankData(remainingHandles, jankData);
-        mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles, jankData);
+        mFlinger->getTransactionCallbackInvoker().addCallbackHandles(remainingHandles);
     }
 
     mReleasePreviousBuffer = false;
@@ -3541,14 +1179,6 @@
     return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
 }
 
-FloatRect Layer::computeSourceBounds(const FloatRect& parentBounds) const {
-    if (mBufferInfo.mBuffer == nullptr) {
-        return parentBounds;
-    }
-
-    return getBufferSize(getDrawingState()).toFloatRect();
-}
-
 bool Layer::fenceHasSignaled() const {
     if (SurfaceFlinger::enableLatchUnsignaledConfig != LatchUnsignaledConfig::Disabled) {
         return true;
@@ -3570,37 +1200,21 @@
     }
 }
 
-void Layer::setAutoRefresh(bool autoRefresh) {
-    mDrawingState.autoRefresh = autoRefresh;
-}
-
 bool Layer::latchSidebandStream(bool& recomputeVisibleRegions) {
-    // We need to update the sideband stream if the layer has both a buffer and a sideband stream.
-    auto* snapshot = editLayerSnapshot();
-    snapshot->sidebandStreamHasFrame = hasFrameUpdate() && mSidebandStream.get();
-
     if (mSidebandStreamChanged.exchange(false)) {
         const State& s(getDrawingState());
         // mSidebandStreamChanged was true
         mSidebandStream = s.sidebandStream;
-        snapshot->sidebandStream = mSidebandStream;
         if (mSidebandStream != nullptr) {
             setTransactionFlags(eTransactionNeeded);
             mFlinger->setTransactionFlags(eTraversalNeeded);
         }
         recomputeVisibleRegions = true;
-
         return true;
     }
     return false;
 }
 
-bool Layer::hasFrameUpdate() const {
-    const State& c(getDrawingState());
-    return (mDrawingStateModified || mDrawingState.modified) &&
-            (c.buffer != nullptr || c.bgColorLayer != nullptr);
-}
-
 void Layer::updateTexImage(nsecs_t latchTime, bool bgColorOnly) {
     const State& s(getDrawingState());
 
@@ -3647,8 +1261,6 @@
     mFlinger->getTransactionCallbackInvoker()
             .addOnCommitCallbackHandles(mDrawingState.callbackHandles, remainingHandles);
     mDrawingState.callbackHandles = remainingHandles;
-
-    mDrawingStateModified = false;
 }
 
 void Layer::gatherBufferInfo() {
@@ -3672,7 +1284,6 @@
     mBufferInfo.mFrameLatencyNeeded = true;
     mBufferInfo.mDesiredPresentTime = mDrawingState.desiredPresentTime;
     mBufferInfo.mFenceTime = std::make_shared<FenceTime>(mDrawingState.acquireFence);
-    mBufferInfo.mFence = mDrawingState.acquireFence;
     mBufferInfo.mTransform = mDrawingState.bufferTransform;
     auto lastDataspace = mBufferInfo.mDataspace;
     mBufferInfo.mDataspace = translateDataspace(mDrawingState.dataspace);
@@ -3683,12 +1294,12 @@
         ui::Dataspace dataspace = ui::Dataspace::UNKNOWN;
         status_t err = OK;
         {
-            ATRACE_NAME("getDataspace");
+            SFTRACE_NAME("getDataspace");
             err = mapper.getDataspace(mBufferInfo.mBuffer->getBuffer()->handle, &dataspace);
         }
         if (err != OK || dataspace != mBufferInfo.mDataspace) {
             {
-                ATRACE_NAME("setDataspace");
+                SFTRACE_NAME("setDataspace");
                 err = mapper.setDataspace(mBufferInfo.mBuffer->getBuffer()->handle,
                                           static_cast<ui::Dataspace>(mBufferInfo.mDataspace));
             }
@@ -3720,10 +1331,6 @@
         mFlinger->mHdrLayerInfoChanged = true;
     }
     mBufferInfo.mCrop = computeBufferCrop(mDrawingState);
-    mBufferInfo.mScaleMode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
-    mBufferInfo.mSurfaceDamage = mDrawingState.surfaceDamageRegion;
-    mBufferInfo.mHdrMetadata = mDrawingState.hdrMetadata;
-    mBufferInfo.mApi = mDrawingState.api;
     mBufferInfo.mTransformToDisplayInverse = mDrawingState.transformToDisplayInverse;
 }
 
@@ -3739,307 +1346,13 @@
     }
 }
 
-sp<Layer> Layer::createClone(uint32_t mirrorRootId) {
-    surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
-                                           LayerMetadata());
-    sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
-    layer->setInitialValuesForClone(sp<Layer>::fromExisting(this), mirrorRootId);
-    return layer;
-}
-
 void Layer::decrementPendingBufferCount() {
     int32_t pendingBuffers = --mPendingBufferTransactions;
     tracePendingBufferCount(pendingBuffers);
 }
 
 void Layer::tracePendingBufferCount(int32_t pendingBuffers) {
-    ATRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
-}
-
-/*
- * We don't want to send the layer's transform to input, but rather the
- * parent's transform. This is because Layer's transform is
- * information about how the buffer is placed on screen. The parent's
- * transform makes more sense to send since it's information about how the
- * layer is placed on screen. This transform is used by input to determine
- * how to go from screen space back to window space.
- */
-ui::Transform Layer::getInputTransform() const {
-    if (!hasBufferOrSidebandStream()) {
-        return getTransform();
-    }
-    sp<Layer> parent = mDrawingParent.promote();
-    if (parent == nullptr) {
-        return ui::Transform();
-    }
-
-    return parent->getTransform();
-}
-
-/**
- * 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.
- */
-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);
-    }
-
-    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{};
-    }
-
-    // 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::isSimpleBufferUpdate(const layer_state_t& s) const {
-    const uint64_t requiredFlags = layer_state_t::eBufferChanged;
-
-    const uint64_t deniedFlags = layer_state_t::eProducerDisconnect | layer_state_t::eLayerChanged |
-            layer_state_t::eRelativeLayerChanged | layer_state_t::eTransparentRegionChanged |
-            layer_state_t::eFlagsChanged | layer_state_t::eBlurRegionsChanged |
-            layer_state_t::eLayerStackChanged | layer_state_t::eAutoRefreshChanged |
-            layer_state_t::eReparent;
-
-    if ((s.what & requiredFlags) != requiredFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
-                              (s.what | requiredFlags) & ~s.what);
-        return false;
-    }
-
-    if (s.what & deniedFlags) {
-        ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
-                              s.what & deniedFlags);
-        return false;
-    }
-
-    if (s.what & layer_state_t::ePositionChanged) {
-        if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) {
-            ATRACE_FORMAT_INSTANT("%s: false [ePositionChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eAlphaChanged) {
-        if (mDrawingState.color.a != s.color.a) {
-            ATRACE_FORMAT_INSTANT("%s: false [eAlphaChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eColorTransformChanged) {
-        if (mDrawingState.colorTransform != s.colorTransform) {
-            ATRACE_FORMAT_INSTANT("%s: false [eColorTransformChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eBackgroundColorChanged) {
-        if (mDrawingState.bgColorLayer || s.bgColor.a != 0) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBackgroundColorChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eMatrixChanged) {
-        if (mRequestedTransform.dsdx() != s.matrix.dsdx ||
-            mRequestedTransform.dtdy() != s.matrix.dtdy ||
-            mRequestedTransform.dtdx() != s.matrix.dtdx ||
-            mRequestedTransform.dsdy() != s.matrix.dsdy) {
-            ATRACE_FORMAT_INSTANT("%s: false [eMatrixChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eCornerRadiusChanged) {
-        if (mDrawingState.cornerRadius != s.cornerRadius) {
-            ATRACE_FORMAT_INSTANT("%s: false [eCornerRadiusChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) {
-        if (mDrawingState.backgroundBlurRadius != static_cast<int>(s.backgroundBlurRadius)) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eBufferTransformChanged) {
-        if (mDrawingState.bufferTransform != s.bufferTransform) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBufferTransformChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eTransformToDisplayInverseChanged) {
-        if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) {
-            ATRACE_FORMAT_INSTANT("%s: false [eTransformToDisplayInverseChanged changed]",
-                                  __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eCropChanged) {
-        if (mDrawingState.crop != s.crop) {
-            ATRACE_FORMAT_INSTANT("%s: false [eCropChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eDataspaceChanged) {
-        if (mDrawingState.dataspace != s.dataspace) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDataspaceChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eHdrMetadataChanged) {
-        if (mDrawingState.hdrMetadata != s.hdrMetadata) {
-            ATRACE_FORMAT_INSTANT("%s: false [eHdrMetadataChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eSidebandStreamChanged) {
-        if (mDrawingState.sidebandStream != s.sidebandStream) {
-            ATRACE_FORMAT_INSTANT("%s: false [eSidebandStreamChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eColorSpaceAgnosticChanged) {
-        if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) {
-            ATRACE_FORMAT_INSTANT("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eShadowRadiusChanged) {
-        if (mDrawingState.shadowRadius != s.shadowRadius) {
-            ATRACE_FORMAT_INSTANT("%s: false [eShadowRadiusChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eFixedTransformHintChanged) {
-        if (mDrawingState.fixedTransformHint != s.fixedTransformHint) {
-            ATRACE_FORMAT_INSTANT("%s: false [eFixedTransformHintChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eTrustedOverlayChanged) {
-        if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) {
-            ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eStretchChanged) {
-        StretchEffect temp = s.stretchEffect;
-        temp.sanitize();
-        if (mDrawingState.stretchEffect != temp) {
-            ATRACE_FORMAT_INSTANT("%s: false [eStretchChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eBufferCropChanged) {
-        if (mDrawingState.bufferCrop != s.bufferCrop) {
-            ATRACE_FORMAT_INSTANT("%s: false [eBufferCropChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eDestinationFrameChanged) {
-        if (mDrawingState.destinationFrame != s.destinationFrame) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDestinationFrameChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eDimmingEnabledChanged) {
-        if (mDrawingState.dimmingEnabled != s.dimmingEnabled) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDimmingEnabledChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
-        if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio ||
-            mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
-            ATRACE_FORMAT_INSTANT("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    if (s.what & layer_state_t::eDesiredHdrHeadroomChanged) {
-        if (mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
-            ATRACE_FORMAT_INSTANT("%s: false [eDesiredHdrHeadroomChanged changed]", __func__);
-            return false;
-        }
-    }
-
-    return true;
-}
-
-sp<LayerFE> Layer::getCompositionEngineLayerFE() const {
-    // There's no need to get a CE Layer if the layer isn't going to draw anything.
-    return hasSomethingToDraw() ? mLegacyLayerFE : nullptr;
-}
-
-const LayerSnapshot* Layer::getLayerSnapshot() const {
-    return mSnapshot.get();
-}
-
-LayerSnapshot* Layer::editLayerSnapshot() {
-    return mSnapshot.get();
-}
-
-std::unique_ptr<frontend::LayerSnapshot> Layer::stealLayerSnapshot() {
-    return std::move(mSnapshot);
-}
-
-void Layer::updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot) {
-    mSnapshot = std::move(snapshot);
-}
-
-const compositionengine::LayerFECompositionState* Layer::getCompositionState() const {
-    return mSnapshot.get();
-}
-
-sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
-    auto result = mFlinger->getFactory().createLayerFE(mName);
-    result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
-    return result;
+    SFTRACE_INT(mBlastTransactionName.c_str(), pendingBuffers);
 }
 
 sp<LayerFE> Layer::getCompositionEngineLayerFE(
@@ -4049,64 +1362,16 @@
             return layerFE;
         }
     }
-    auto layerFE = mFlinger->getFactory().createLayerFE(mName);
+    auto layerFE = mFlinger->getFactory().createLayerFE(mName, this);
     mLayerFEs.emplace_back(path, layerFE);
     return layerFE;
 }
 
-void Layer::useSurfaceDamage() {
-    if (mFlinger->mForceFullDamage) {
-        surfaceDamageRegion = Region::INVALID_REGION;
-    } else {
-        surfaceDamageRegion = mBufferInfo.mSurfaceDamage;
-    }
-}
-
-void Layer::useEmptyDamage() {
-    surfaceDamageRegion.clear();
-}
-
-bool Layer::isOpaque(const Layer::State& s) const {
-    // if we don't have a buffer or sidebandStream yet, we're translucent regardless of the
-    // layer's opaque flag.
-    if (!hasSomethingToDraw()) {
-        return false;
-    }
-
-    // if the layer has the opaque flag, then we're always opaque
-    if ((s.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque) {
-        return true;
-    }
-
-    // If the buffer has no alpha channel, then we are opaque
-    if (hasBufferOrSidebandStream() && LayerSnapshot::isOpaqueFormat(getPixelFormat())) {
-        return true;
-    }
-
-    // Lastly consider the layer opaque if drawing a color with alpha == 1.0
-    return fillsColor() && getAlpha() == 1.0_hf;
-}
-
-bool Layer::canReceiveInput() const {
-    return !isHiddenByPolicy() && (mBufferInfo.mBuffer == nullptr || getAlpha() > 0.0f);
-}
-
-bool Layer::isVisible() const {
-    if (!hasSomethingToDraw()) {
-        return false;
-    }
-
-    if (isHiddenByPolicy()) {
-        return false;
-    }
-
-    return getAlpha() > 0.0f || hasBlur();
-}
-
 void Layer::onCompositionPresented(const DisplayDevice* display,
                                    const std::shared_ptr<FenceTime>& glDoneFence,
                                    const std::shared_ptr<FenceTime>& presentFence,
-                                   const CompositorTiming& compositorTiming) {
+                                   const CompositorTiming& compositorTiming,
+                                   gui::GameMode gameMode) {
     // mFrameLatencyNeeded is true when a new frame was latched for the
     // composition.
     if (!mBufferInfo.mFrameLatencyNeeded) return;
@@ -4148,12 +1413,12 @@
     }
 
     if (display) {
-        const Fps refreshRate = display->refreshRateSelector().getActiveMode().fps;
+        const auto activeMode = display->refreshRateSelector().getActiveMode();
+        const Fps refreshRate = activeMode.fps;
         const std::optional<Fps> renderRate =
                 mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
 
         const auto vote = frameRateToSetFrameRateVotePayload(getFrameRateForLayerTree());
-        const auto gameMode = getGameMode();
 
         if (presentFence->isValid()) {
             mFlinger->mTimeStats->setPresentFence(layerId, mCurrentFrameNumber, presentFence,
@@ -4169,7 +1434,12 @@
                     mFlinger->getHwComposer().getPresentTimestamp(*displayId);
 
             const nsecs_t now = systemTime(CLOCK_MONOTONIC);
-            const nsecs_t vsyncPeriod = display->getVsyncPeriodFromHWC();
+            const nsecs_t vsyncPeriod =
+                    mFlinger->getHwComposer()
+                            .getDisplayVsyncPeriod(*displayId)
+                            .value_opt()
+                            .value_or(activeMode.modePtr->getVsyncRate().getPeriodNsecs());
+
             const nsecs_t actualPresentTime = now - ((now - presentTimestamp) % vsyncPeriod);
 
             mFlinger->mTimeStats->setPresentTime(layerId, mCurrentFrameNumber, actualPresentTime,
@@ -4185,18 +1455,9 @@
     mBufferInfo.mFrameLatencyNeeded = false;
 }
 
-bool Layer::willReleaseBufferOnLatch() const {
-    return !mDrawingState.buffer && mBufferInfo.mBuffer;
-}
-
-bool Layer::latchBuffer(bool& recomputeVisibleRegions, nsecs_t latchTime) {
-    const bool bgColorOnly = mDrawingState.bgColorLayer != nullptr;
-    return latchBufferImpl(recomputeVisibleRegions, latchTime, bgColorOnly);
-}
-
 bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bool bgColorOnly) {
-    ATRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
-                          getDrawingState().frameNumber);
+    SFTRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
+                           getDrawingState().frameNumber);
 
     bool refreshRequired = latchSidebandStream(recomputeVisibleRegions);
 
@@ -4207,7 +1468,7 @@
     // If the head buffer's acquire fence hasn't signaled yet, return and
     // try again later
     if (!fenceHasSignaled()) {
-        ATRACE_NAME("!fenceHasSignaled()");
+        SFTRACE_NAME("!fenceHasSignaled()");
         mFlinger->onLayerUpdate();
         return false;
     }
@@ -4215,7 +1476,6 @@
 
     // Capture the old state of the layer for comparisons later
     BufferInfo oldBufferInfo = mBufferInfo;
-    const bool oldOpacity = isOpaque(mDrawingState);
     mPreviousFrameNumber = mCurrentFrameNumber;
     mCurrentFrameNumber = mDrawingState.frameNumber;
     gatherBufferInfo();
@@ -4240,7 +1500,6 @@
 
     if ((mBufferInfo.mCrop != oldBufferInfo.mCrop) ||
         (mBufferInfo.mTransform != oldBufferInfo.mTransform) ||
-        (mBufferInfo.mScaleMode != oldBufferInfo.mScaleMode) ||
         (mBufferInfo.mTransformToDisplayInverse != oldBufferInfo.mTransformToDisplayInverse)) {
         recomputeVisibleRegions = true;
     }
@@ -4253,70 +1512,13 @@
             recomputeVisibleRegions = true;
         }
     }
-
-    if (oldOpacity != isOpaque(mDrawingState)) {
-        recomputeVisibleRegions = true;
-    }
-
     return true;
 }
 
-bool Layer::hasReadyFrame() const {
-    return hasFrameUpdate() || getSidebandStreamChanged() || getAutoRefresh();
-}
-
-bool Layer::isProtected() const {
-    return (mBufferInfo.mBuffer != nullptr) &&
-            (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
-}
-
-void Layer::latchAndReleaseBuffer() {
-    if (hasReadyFrame()) {
-        bool ignored = false;
-        latchBuffer(ignored, systemTime());
-    }
-    releasePendingBuffer(systemTime());
-}
-
-PixelFormat Layer::getPixelFormat() const {
-    return mBufferInfo.mPixelFormat;
-}
-
 bool Layer::getTransformToDisplayInverse() const {
     return mBufferInfo.mTransformToDisplayInverse;
 }
 
-Rect Layer::getBufferCrop() const {
-    // this is the crop rectangle that applies to the buffer
-    // itself (as opposed to the window)
-    if (!mBufferInfo.mCrop.isEmpty()) {
-        // if the buffer crop is defined, we use that
-        return mBufferInfo.mCrop;
-    } else if (mBufferInfo.mBuffer != nullptr) {
-        // otherwise we use the whole buffer
-        return mBufferInfo.mBuffer->getBounds();
-    } else {
-        // if we don't have a buffer yet, we use an empty/invalid crop
-        return Rect();
-    }
-}
-
-uint32_t Layer::getBufferTransform() const {
-    return mBufferInfo.mTransform;
-}
-
-ui::Dataspace Layer::getDataSpace() const {
-    return hasBufferOrSidebandStream() ? mBufferInfo.mDataspace : mDrawingState.dataspace;
-}
-
-bool Layer::isFrontBuffered() const {
-    if (mBufferInfo.mBuffer == nullptr) {
-        return false;
-    }
-
-    return mBufferInfo.mBuffer->getUsage() & AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
-}
-
 ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) {
     ui::Dataspace updatedDataspace = dataspace;
     // translate legacy dataspaces to modern dataspaces
@@ -4352,124 +1554,6 @@
     return mBufferInfo.mBuffer ? mBufferInfo.mBuffer->getBuffer() : nullptr;
 }
 
-void Layer::setTransformHintLegacy(ui::Transform::RotationFlags displayTransformHint) {
-    mTransformHintLegacy = getFixedTransformHint();
-    if (mTransformHintLegacy == ui::Transform::ROT_INVALID) {
-        mTransformHintLegacy = displayTransformHint;
-    }
-    mSkipReportingTransformHint = false;
-}
-
-const std::shared_ptr<renderengine::ExternalTexture>& Layer::getExternalTexture() const {
-    return mBufferInfo.mBuffer;
-}
-
-bool Layer::setColor(const half3& color) {
-    if (mDrawingState.color.rgb == color) {
-        return false;
-    }
-
-    mDrawingState.sequence++;
-    mDrawingState.color.rgb = color;
-    mDrawingState.modified = true;
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
-bool Layer::fillsColor() const {
-    return !hasBufferOrSidebandStream() && mDrawingState.color.r >= 0.0_hf &&
-            mDrawingState.color.g >= 0.0_hf && mDrawingState.color.b >= 0.0_hf;
-}
-
-bool Layer::hasBlur() const {
-    return getBackgroundBlurRadius() > 0 || getDrawingState().blurRegions.size() > 0;
-}
-
-void Layer::updateSnapshot(bool updateGeometry) {
-    if (!getCompositionEngineLayerFE()) {
-        return;
-    }
-
-    auto* snapshot = editLayerSnapshot();
-    if (updateGeometry) {
-        prepareBasicGeometryCompositionState();
-        prepareGeometryCompositionState();
-        snapshot->roundedCorner = getRoundedCornerState();
-        snapshot->transformedBounds = mScreenBounds;
-        if (mEffectiveShadowRadius > 0.f) {
-            snapshot->shadowSettings = mFlinger->mDrawingState.globalShadowSettings;
-
-            // Note: this preserves existing behavior of shadowing the entire layer and not cropping
-            // it if transparent regions are present. This may not be necessary since shadows are
-            // typically cast by layers without transparent regions.
-            snapshot->shadowSettings.boundaries = mBounds;
-
-            const float casterAlpha = snapshot->alpha;
-            const bool casterIsOpaque =
-                    ((mBufferInfo.mBuffer != nullptr) && isOpaque(mDrawingState));
-
-            // If the casting layer is translucent, we need to fill in the shadow underneath the
-            // layer. Otherwise the generated shadow will only be shown around the casting layer.
-            snapshot->shadowSettings.casterIsTranslucent = !casterIsOpaque || (casterAlpha < 1.0f);
-            snapshot->shadowSettings.ambientColor *= casterAlpha;
-            snapshot->shadowSettings.spotColor *= casterAlpha;
-        }
-        snapshot->shadowSettings.length = mEffectiveShadowRadius;
-    }
-    snapshot->contentOpaque = isOpaque(mDrawingState);
-    snapshot->layerOpaqueFlagSet =
-            (mDrawingState.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
-    sp<Layer> p = mDrawingParent.promote();
-    if (p != nullptr) {
-        snapshot->parentTransform = p->getTransform();
-    } else {
-        snapshot->parentTransform.reset();
-    }
-    snapshot->bufferSize = getBufferSize(mDrawingState);
-    snapshot->externalTexture = mBufferInfo.mBuffer;
-    snapshot->hasReadyFrame = hasReadyFrame();
-    preparePerFrameCompositionState();
-}
-
-void Layer::updateChildrenSnapshots(bool updateGeometry) {
-    for (const sp<Layer>& child : mDrawingChildren) {
-        child->updateSnapshot(updateGeometry);
-        child->updateChildrenSnapshots(updateGeometry);
-    }
-}
-
-void Layer::updateMetadataSnapshot(const LayerMetadata& parentMetadata) {
-    mSnapshot->layerMetadata = parentMetadata;
-    mSnapshot->layerMetadata.merge(mDrawingState.metadata);
-    for (const sp<Layer>& child : mDrawingChildren) {
-        child->updateMetadataSnapshot(mSnapshot->layerMetadata);
-    }
-}
-
-void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
-                                           std::unordered_set<Layer*>& visited) {
-    if (visited.find(this) != visited.end()) {
-        ALOGW("Cycle containing layer %s detected in z-order relatives", getDebugName());
-        return;
-    }
-    visited.insert(this);
-
-    mSnapshot->relativeLayerMetadata = relativeLayerMetadata;
-
-    if (mDrawingState.zOrderRelatives.empty()) {
-        return;
-    }
-    LayerMetadata childRelativeLayerMetadata = mSnapshot->relativeLayerMetadata;
-    childRelativeLayerMetadata.merge(mSnapshot->layerMetadata);
-    for (wp<Layer> weakRelative : mDrawingState.zOrderRelatives) {
-        sp<Layer> relative = weakRelative.promote();
-        if (!relative) {
-            continue;
-        }
-        relative->updateRelativeMetadataSnapshot(childRelativeLayerMetadata, visited);
-    }
-}
-
 bool Layer::setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
                                        TrustedPresentationListener const& listener) {
     bool hadTrustedPresentationListener = hasTrustedPresentationListener();
@@ -4493,39 +1577,41 @@
     return haveTrustedPresentationListener;
 }
 
+void Layer::setBufferReleaseChannel(
+        const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel) {
+    mBufferReleaseChannel = channel;
+}
+
 void Layer::updateLastLatchTime(nsecs_t latchTime) {
     mLastLatchTime = latchTime;
 }
 
-void Layer::setIsSmallDirty(const Region& damageRegion,
-                            const ui::Transform& layerToDisplayTransform) {
-    mSmallDirty = false;
+void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
     if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
+        snapshot->isSmallDirty = false;
         return;
     }
 
     if (mWindowType != WindowInfo::Type::APPLICATION &&
         mWindowType != WindowInfo::Type::BASE_APPLICATION) {
+        snapshot->isSmallDirty = false;
         return;
     }
 
-    Rect bounds = damageRegion.getBounds();
+    Rect bounds = snapshot->surfaceDamage.getBounds();
     if (!bounds.isValid()) {
+        snapshot->isSmallDirty = false;
         return;
     }
 
     // Transform to screen space.
-    bounds = layerToDisplayTransform.transform(bounds);
+    bounds = snapshot->localTransform.transform(bounds);
 
     // If the damage region is a small dirty, this could give the hint for the layer history that
     // it could suppress the heuristic rate when calculating.
-    mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
-                                                         bounds.getWidth() * bounds.getHeight());
-}
-
-void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
-    setIsSmallDirty(snapshot->surfaceDamage, snapshot->localTransform);
-    snapshot->isSmallDirty = mSmallDirty;
+    snapshot->isSmallDirty =
+            mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
+                                                   bounds.getWidth() * bounds.getHeight());
 }
 
 } // namespace android
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 0ceecec..9bc557e 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -18,6 +18,7 @@
 
 #include <android/gui/DropInputMode.h>
 #include <android/gui/ISurfaceComposerClient.h>
+#include <ftl/small_map.h>
 #include <gui/BufferQueue.h>
 #include <gui/LayerState.h>
 #include <gui/WindowInfo.h>
@@ -25,9 +26,11 @@
 #include <math/vec4.h>
 #include <sys/types.h>
 #include <ui/BlurRegion.h>
+#include <ui/DisplayMap.h>
 #include <ui/FloatRect.h>
 #include <ui/FrameStats.h>
 #include <ui/GraphicBuffer.h>
+#include <ui/LayerStack.h>
 #include <ui/PixelFormat.h>
 #include <ui/Region.h>
 #include <ui/StretchEffect.h>
@@ -40,9 +43,7 @@
 #include <scheduler/Fps.h>
 #include <scheduler/Seamlessness.h>
 
-#include <chrono>
 #include <cstdint>
-#include <list>
 #include <optional>
 #include <vector>
 
@@ -53,7 +54,6 @@
 #include "LayerVector.h"
 #include "Scheduler/LayerInfo.h"
 #include "SurfaceFlinger.h"
-#include "Tracing/LayerTracing.h"
 #include "TransactionCallbackInvoker.h"
 
 using namespace android::surfaceflinger;
@@ -71,10 +71,6 @@
 struct LayerFECompositionState;
 }
 
-namespace gui {
-class LayerDebugInfo;
-}
-
 namespace frametimeline {
 class SurfaceFrame;
 } // namespace frametimeline
@@ -92,48 +88,15 @@
     // Windows that are not in focus, but voted for a specific mode ID.
     static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
 
-    enum { // flags for doTransaction()
-        eDontUpdateGeometryState = 0x00000001,
-        eVisibleRegion = 0x00000002,
-        eInputInfoChanged = 0x00000004
-    };
-
-    struct Geometry {
-        uint32_t w;
-        uint32_t h;
-        ui::Transform transform;
-
-        inline bool operator==(const Geometry& rhs) const {
-            return (w == rhs.w && h == rhs.h) && (transform.tx() == rhs.transform.tx()) &&
-                    (transform.ty() == rhs.transform.ty());
-        }
-        inline bool operator!=(const Geometry& rhs) const { return !operator==(rhs); }
-    };
-
     using FrameRate = scheduler::LayerInfo::FrameRate;
     using FrameRateCompatibility = scheduler::FrameRateCompatibility;
     using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
 
     struct State {
-        int32_t z;
-        ui::LayerStack layerStack;
-        uint32_t flags;
         int32_t sequence; // changes when visible regions can change
-        bool modified;
         // Crop is expressed in layer space coordinate.
         Rect crop;
         LayerMetadata metadata;
-        // If non-null, a Surface this Surface's Z-order is interpreted relative to.
-        wp<Layer> zOrderRelativeOf;
-        bool isRelativeOf{false};
-
-        // A list of surfaces whose Z-order is interpreted relative to ours.
-        SortedVector<wp<Layer>> zOrderRelatives;
-        half4 color;
-        float cornerRadius;
-        int backgroundBlurRadius;
-        gui::WindowInfo inputInfo;
-        wp<Layer> touchableRegionCrop;
 
         ui::Dataspace dataspace;
 
@@ -155,52 +118,18 @@
         std::shared_ptr<renderengine::ExternalTexture> buffer;
         sp<Fence> acquireFence;
         std::shared_ptr<FenceTime> acquireFenceTime;
-        HdrMetadata hdrMetadata;
-        Region surfaceDamageRegion;
-        int32_t api;
         sp<NativeHandle> sidebandStream;
         mat4 colorTransform;
-        bool hasColorTransform;
-        // pointer to background color layer that, if set, appears below the buffer state layer
-        // and the buffer state layer's children.  Z order will be set to
-        // INT_MIN
-        sp<Layer> bgColorLayer;
 
         // The deque of callback handles for this frame. The back of the deque contains the most
         // recent callback handle.
         std::deque<sp<CallbackHandle>> callbackHandles;
-        bool colorSpaceAgnostic;
         nsecs_t desiredPresentTime = 0;
         bool isAutoTimestamp = true;
 
-        // Length of the cast shadow. If the radius is > 0, a shadow of length shadowRadius will
-        // be rendered around the layer.
-        float shadowRadius;
-
-        // Layer regions that are made of custom materials, like frosted glass
-        std::vector<BlurRegion> blurRegions;
-
-        // Priority of the layer assigned by Window Manager.
-        int32_t frameRateSelectionPriority;
-
-        // Default frame rate compatibility used to set the layer refresh rate votetype.
-        FrameRateCompatibility defaultFrameRateCompatibility;
-        FrameRate frameRate;
-
         // The combined frame rate of parents / children of this layer
         FrameRate frameRateForLayerTree;
 
-        FrameRateSelectionStrategy frameRateSelectionStrategy;
-
-        // Set by window manager indicating the layer and all its children are
-        // in a different orientation than the display. The hint suggests that
-        // the graphic producers should receive a transform hint as if the
-        // display was in this orientation. When the display changes to match
-        // the layer orientation, the graphic producer may not need to allocate
-        // a buffer of a different size. ui::Transform::ROT_INVALID means the
-        // a fixed transform hint is not set.
-        ui::Transform::RotationFlags fixedTransformHint;
-
         // The vsync info that was used to start the transaction
         FrameTimelineInfo frameTimelineInfo;
 
@@ -220,21 +149,12 @@
         // An arbitrary threshold for the number of BufferlessSurfaceFrames in the state. Used to
         // trigger a warning if the number of SurfaceFrames crosses the threshold.
         static constexpr uint32_t kStateSurfaceFramesThreshold = 25;
-
-        // Stretch effect to apply to this layer
-        StretchEffect stretchEffect;
-
-        // Whether or not this layer is a trusted overlay for input
-        bool isTrustedOverlay;
         Rect bufferCrop;
         Rect destinationFrame;
         sp<IBinder> releaseBufferEndpoint;
-        gui::DropInputMode dropInputMode;
         bool autoRefresh = false;
-        bool dimmingEnabled = true;
         float currentHdrSdrRatio = 1.f;
         float desiredHdrSdrRatio = -1.f;
-        gui::CachingHint cachingHint = gui::CachingHint::Enabled;
         int64_t latchedVsyncId = 0;
         bool useVsyncIdForRefreshRateSelection = false;
     };
@@ -245,202 +165,49 @@
     static bool isLayerFocusedBasedOnPriority(int32_t priority);
     static void miniDumpHeader(std::string& result);
 
-    // Provide unique string for each class type in the Layer hierarchy
-    virtual const char* getType() const { return "Layer"; }
-
-    // true if this layer is visible, false otherwise
-    virtual bool isVisible() const;
-
-    virtual sp<Layer> createClone(uint32_t mirrorRoot);
-
-    // Set a 2x2 transformation matrix on the layer. This transform
-    // will be applied after parent transforms, but before any final
-    // producer specified transform.
-    bool setMatrix(const layer_state_t::matrix22_t& matrix);
-
     // This second set of geometry attributes are controlled by
     // setGeometryAppliesWithResize, and their default mode is to be
     // immediate. If setGeometryAppliesWithResize is specified
     // while a resize is pending, then update of these attributes will
     // be delayed until the resize completes.
 
-    // setPosition operates in parent buffer space (pre parent-transform) or display
-    // space for top-level layers.
-    bool setPosition(float x, float y);
     // Buffer space
     bool setCrop(const Rect& crop);
 
-    // TODO(b/38182121): Could we eliminate the various latching modes by
-    // using the layer hierarchy?
-    // -----------------------------------------------------------------------
-    virtual bool setLayer(int32_t z);
-    virtual bool setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relativeZ);
-
-    virtual bool setAlpha(float alpha);
-    bool setColor(const half3& /*color*/);
-
-    // Set rounded corner radius for this layer and its children.
-    //
-    // We only support 1 radius per layer in the hierarchy, where parent layers have precedence.
-    // The shape of the rounded corner rectangle is specified by the crop rectangle of the layer
-    // from which we inferred the rounded corner radius.
-    virtual bool setCornerRadius(float cornerRadius);
-    // When non-zero, everything below this layer will be blurred by backgroundBlurRadius, which
-    // is specified in pixels.
-    virtual bool setBackgroundBlurRadius(int backgroundBlurRadius);
-    virtual bool setBlurRegions(const std::vector<BlurRegion>& effectRegions);
-    bool setTransparentRegionHint(const Region& transparent);
-    virtual bool setTrustedOverlay(bool);
-    virtual bool setFlags(uint32_t flags, uint32_t mask);
-    virtual bool setLayerStack(ui::LayerStack);
-    virtual ui::LayerStack getLayerStack(
-            LayerVector::StateSet state = LayerVector::StateSet::Drawing) const;
-
-    virtual bool setMetadata(const LayerMetadata& data);
-    virtual void setChildrenDrawingParent(const sp<Layer>&);
-    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 getDesiredHdrSdrRatio() const { return getDrawingState().desiredHdrSdrRatio; }
-    float getCurrentHdrSdrRatio() const { return getDrawingState().currentHdrSdrRatio; }
-    gui::CachingHint getCachingHint() const { return getDrawingState().cachingHint; }
-
     bool setTransform(uint32_t /*transform*/);
     bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
     bool setBuffer(std::shared_ptr<renderengine::ExternalTexture>& /* buffer */,
                    const BufferData& /* bufferData */, nsecs_t /* postTime */,
                    nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
-                   std::optional<nsecs_t> /* dequeueTime */, const FrameTimelineInfo& /*info*/);
+                   const FrameTimelineInfo& /*info*/, gui::GameMode gameMode);
     void setDesiredPresentTime(nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/);
     bool setDataspace(ui::Dataspace /*dataspace*/);
     bool setExtendedRangeBrightness(float currentBufferRatio, float desiredRatio);
     bool setDesiredHdrHeadroom(float desiredRatio);
-    bool setCachingHint(gui::CachingHint cachingHint);
-    bool setHdrMetadata(const HdrMetadata& /*hdrMetadata*/);
-    bool setSurfaceDamageRegion(const Region& /*surfaceDamage*/);
-    bool setApi(int32_t /*api*/);
     bool setSidebandStream(const sp<NativeHandle>& /*sidebandStream*/,
-                           const FrameTimelineInfo& /* info*/, nsecs_t /* postTime */);
+                           const FrameTimelineInfo& /* info*/, nsecs_t /* postTime */,
+                           gui::GameMode gameMode);
     bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*handles*/,
                                           bool willPresent);
-    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);
-    virtual bool setFrameRateSelectionPriority(int32_t priority);
-    virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint);
-    void setAutoRefresh(bool /* autoRefresh */);
-    bool setDropInputMode(gui::DropInputMode);
 
-    //  If the variable is not set on the layer, it traverses up the tree to inherit the frame
-    //  rate priority from its parent.
-    virtual int32_t getFrameRateSelectionPriority() const;
-    //
-    virtual FrameRateCompatibility getDefaultFrameRateCompatibility() const;
-    //
-    ui::Dataspace getDataSpace() const;
-
-    virtual bool isFrontBuffered() const;
-
-    virtual sp<LayerFE> getCompositionEngineLayerFE() const;
-    virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
     sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
-    sp<LayerFE> getOrCreateCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
-
-    const frontend::LayerSnapshot* getLayerSnapshot() const;
-    frontend::LayerSnapshot* editLayerSnapshot();
-    std::unique_ptr<frontend::LayerSnapshot> stealLayerSnapshot();
-    void updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot);
 
     // If we have received a new buffer this frame, we will pass its surface
     // damage down to hardware composer. Otherwise, we must send a region with
     // one empty rect.
-    void useSurfaceDamage();
-    void useEmptyDamage();
     Region getVisibleRegion(const DisplayDevice*) const;
     void updateLastLatchTime(nsecs_t latchtime);
 
-    /*
-     * isOpaque - true if this surface is opaque
-     *
-     * This takes into account the buffer format (i.e. whether or not the
-     * pixel format includes an alpha channel) and the "opaque" flag set
-     * on the layer.  It does not examine the current plane alpha value.
-     */
-    bool isOpaque(const Layer::State&) const;
-
-    /*
-     * Returns whether this layer can receive input.
-     */
-    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.
-     */
-    bool isProtected() const;
-
-    /*
-     * isFixedSize - true if content has a fixed size
-     */
-    virtual bool isFixedSize() const { return true; }
-
-    /*
-     * usesSourceCrop - true if content should use a source crop
-     */
-    bool usesSourceCrop() const { return hasBufferOrSidebandStream(); }
-
-    // Most layers aren't created from the main thread, and therefore need to
-    // grab the SF state lock to access HWC, but ContainerLayer does, so we need
-    // to avoid grabbing the lock again to avoid deadlock
-    virtual bool isCreatedFromMainThread() const { return false; }
-
-    ui::Transform getActiveTransform(const Layer::State& s) const { return s.transform; }
-    Region getActiveTransparentRegion(const Layer::State& s) const {
-        return s.transparentRegionHint;
-    }
     Rect getCrop(const Layer::State& s) const { return s.crop; }
-    bool needsFiltering(const DisplayDevice*) const;
-
-    // True if this layer requires filtering
-    // This method is distinct from needsFiltering() in how the filter
-    // requirement is computed. needsFiltering() compares displayFrame and crop,
-    // where as this method transforms the displayFrame to layer-stack space
-    // first. This method should be used if there is no physical display to
-    // project onto when taking screenshots, as the filtering requirements are
-    // different.
-    // If the parent transform needs to be undone when capturing the layer, then
-    // the inverse parent transform is also required.
-    bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const;
 
     // from graphics API
     static ui::Dataspace translateDataspace(ui::Dataspace dataspace);
-    void updateCloneBufferInfo();
     uint64_t mPreviousFrameNumber = 0;
 
     void onCompositionPresented(const DisplayDevice*,
                                 const std::shared_ptr<FenceTime>& /*glDoneFence*/,
                                 const std::shared_ptr<FenceTime>& /*presentFence*/,
-                                const CompositorTiming&);
+                                const CompositorTiming&, gui::GameMode gameMode);
 
     // If a buffer was replaced this frame, release the former buffer
     void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/);
@@ -451,46 +218,10 @@
      * operation, so this should be set only if needed). Typically this is used
      * to figure out if the content or size of a surface has changed.
      */
-    bool latchBuffer(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/);
-
     bool latchBufferImpl(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/,
                          bool bgColorOnly);
 
-    /*
-     * Returns true if the currently presented buffer will be released when this layer state
-     * is latched. This will return false if there is no buffer currently presented.
-     */
-    bool willReleaseBufferOnLatch() const;
-
-    /*
-     * Calls latchBuffer if the buffer has a frame queued and then releases the buffer.
-     * This is used if the buffer is just latched and releases to free up the buffer
-     * and will not be shown on screen.
-     * Should only be called on the main thread.
-     */
-    void latchAndReleaseBuffer();
-
-    /*
-     * returns the rectangle that crops the content of the layer and scales it
-     * to the layer's size.
-     */
-    Rect getBufferCrop() const;
-
-    /*
-     * Returns the transform applied to the buffer.
-     */
-    uint32_t getBufferTransform() const;
-
     sp<GraphicBuffer> getBuffer() const;
-    const std::shared_ptr<renderengine::ExternalTexture>& getExternalTexture() const;
-
-    /*
-     * Returns if a frame is ready
-     */
-    bool hasReadyFrame() const;
-
-    virtual int32_t getQueuedFrameCount() const { return 0; }
-
     /**
      * Returns active buffer size in the correct orientation. Buffer size is determined by undoing
      * any buffer transformations. Returns Rect::INVALID_RECT if the layer has no buffer or the
@@ -498,33 +229,10 @@
      */
     Rect getBufferSize(const Layer::State&) const;
 
-    /**
-     * Returns the source bounds. If the bounds are not defined, it is inferred from the
-     * buffer size. Failing that, the bounds are determined from the passed in parent bounds.
-     * For the root layer, this is the display viewport size.
-     */
-    FloatRect computeSourceBounds(const FloatRect& parentBounds) const;
-    virtual FrameRate getFrameRateForLayerTree() const;
+    FrameRate getFrameRateForLayerTree() const;
 
     bool getTransformToDisplayInverse() const;
 
-    // Returns how rounded corners should be drawn for this layer.
-    // A layer can override its parent's rounded corner settings if the parent's rounded
-    // corner crop does not intersect with its own rounded corner crop.
-    virtual frontend::RoundedCornerState getRoundedCornerState() const;
-
-    bool hasRoundedCorners() const { return getRoundedCornerState().hasRoundedCorners(); }
-
-    PixelFormat getPixelFormat() const;
-    /**
-     * Return whether this layer needs an input info. We generate InputWindowHandles for all
-     * non-cursor buffered layers regardless of whether they have an InputChannel. This is to enable
-     * the InputDispatcher to do PID based occlusion detection.
-     */
-    bool needsInputInfo() const {
-        return (hasInputInfo() || hasBufferOrSidebandStream()) && !mPotentialCursor;
-    }
-
     // Implements RefBase.
     void onFirstRef() override;
 
@@ -535,30 +243,31 @@
         uint32_t mTransform{0};
         ui::Dataspace mDataspace{ui::Dataspace::UNKNOWN};
         Rect mCrop;
-        uint32_t mScaleMode{NATIVE_WINDOW_SCALING_MODE_FREEZE};
-        Region mSurfaceDamage;
-        HdrMetadata mHdrMetadata;
-        int mApi;
         PixelFormat mPixelFormat{PIXEL_FORMAT_NONE};
         bool mTransformToDisplayInverse{false};
-
         std::shared_ptr<renderengine::ExternalTexture> mBuffer;
         uint64_t mFrameNumber;
         sp<IBinder> mReleaseBufferEndpoint;
-
         bool mFrameLatencyNeeded{false};
         float mDesiredHdrSdrRatio = -1.f;
     };
 
     BufferInfo mBufferInfo;
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> mBufferReleaseChannel;
 
-    // implements compositionengine::LayerFE
-    const compositionengine::LayerFECompositionState* getCompositionState() const;
     bool fenceHasSignaled() const;
     void onPreComposition(nsecs_t refreshStartTime);
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack,
                           std::function<FenceResult(FenceResult)>&& continuation = nullptr);
 
+    // Tracks mLastClientCompositionFence and gets the callback handle for this layer.
+    sp<CallbackHandle> findCallbackHandle();
+
+    // Adds the future release fence to a list of fences that are used to release the
+    // last presented buffer. Also keeps track of the layerstack in a list of previous
+    // layerstacks that have been presented.
+    void prepareReleaseCallbacks(ftl::Future<FenceResult>, ui::LayerStack layerStack);
+
     void setWasClientComposed(const sp<Fence>& fence) {
         mLastClientCompositionFence = fence;
         mClearClientCompositionFenceOnLayerDisplayed = false;
@@ -566,17 +275,6 @@
 
     const char* getDebugName() const;
 
-    bool setShadowRadius(float shadowRadius);
-
-    // Before color management is introduced, contents on Android have to be
-    // desaturated in order to match what they appears like visually.
-    // With color management, these contents will appear desaturated, thus
-    // needed to be saturated so that they match what they are designed for
-    // visually.
-    bool isLegacyDataSpace() const;
-
-    uint32_t getTransactionFlags() const { return mTransactionFlags; }
-
     static bool computeTrustedPresentationState(const FloatRect& bounds,
                                                 const FloatRect& sourceBounds,
                                                 const Region& coveredRegion,
@@ -594,17 +292,6 @@
     // Sets the masked bits.
     void setTransactionFlags(uint32_t mask);
 
-    // Clears and returns the masked bits.
-    uint32_t clearTransactionFlags(uint32_t mask);
-
-    FloatRect getBounds(const Region& activeTransparentRegion) const;
-    FloatRect getBounds() const;
-    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);
-
     int32_t getSequence() const { return sequence; }
 
     // For tracing.
@@ -615,167 +302,21 @@
     // only used within a single layer.
     uint64_t getCurrentBufferId() const { return getBuffer() ? getBuffer()->getId() : 0; }
 
-    /*
-     * isSecure - true if this surface is secure, that is if it prevents
-     * screenshots or VNC servers. A surface can be set to be secure by the
-     * application, being secure doesn't mean the surface has DRM contents.
-     */
-    bool isSecure() const;
-
-    /*
-     * isHiddenByPolicy - true if this layer has been forced invisible.
-     * just because this is false, doesn't mean isVisible() is true.
-     * For example if this layer has no active buffer, it may not be hidden by
-     * policy, but it still can not be visible.
-     */
-    bool isHiddenByPolicy() const;
-
-    // True if the layer should be skipped in screenshots, screen recordings,
-    // and mirroring to external or virtual displays.
-    bool isInternalDisplayOverlay() const;
-
-    ui::LayerFilter getOutputFilter() const {
-        return {getLayerStack(), isInternalDisplayOverlay()};
-    }
-
-    bool isRemovedFromCurrentState() const;
-
-    perfetto::protos::LayerProto* writeToProto(perfetto::protos::LayersProto& layersProto,
-                                               uint32_t traceFlags);
     void writeCompositionStateToProto(perfetto::protos::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
-    // thread.
-    void writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo);
-    // Write drawing or current state. If writing current state, the caller should hold the
-    // external mStateLock. If writing drawing state, this function should be called on the
-    // main or tracing thread.
-    void writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo, LayerVector::StateSet,
-                                 uint32_t traceFlags = LayerTracing::TRACE_ALL);
-
-    gui::WindowInfo::Type getWindowType() const { return mWindowType; }
-
-    bool updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates);
-
-    /*
-     * doTransaction - process the transaction. This is a good place to figure
-     * out which attributes of the surface have changed.
-     */
-    virtual uint32_t doTransaction(uint32_t transactionFlags);
-
-    /*
-     * Remove relative z for the layer if its relative parent is not part of the
-     * provided layer tree.
-     */
-    void removeRelativeZ(const std::vector<Layer*>& layersInTree);
-
-    /*
-     * Remove from current state and mark for removal.
-     */
-    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() REQUIRES(mFlinger->mStateLock);
-
-    /*
-     * Called when the layer is added back to the current state list.
-     */
-    void addToCurrentState();
-
-    /*
-     * Sets display transform hint on BufferLayerConsumer.
-     */
-    void updateTransformHint(ui::Transform::RotationFlags);
-    void skipReportingTransformHint();
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
-    gui::LayerDebugInfo getLayerDebugInfo(const DisplayDevice*) const;
-
-    void miniDumpLegacy(std::string& result, const DisplayDevice&) const;
     void miniDump(std::string& result, const frontend::LayerSnapshot&, const DisplayDevice&) const;
     void dumpFrameStats(std::string& result) const;
-    void dumpOffscreenDebugInfo(std::string& result) const;
     void clearFrameStats();
     void logFrameStats();
     void getFrameStats(FrameStats* outStats) const;
     void onDisconnect();
 
-    ui::Transform getTransform() const;
-    bool isTransformValid() const;
-
-    // Returns the Alpha of the Surface, accounting for the Alpha
-    // of parent Surfaces in the hierarchy (alpha's will be multiplied
-    // down the hierarchy).
-    half getAlpha() const;
-    half4 getColor() const;
-    int32_t getBackgroundBlurRadius() const;
-    bool drawShadows() const { return mEffectiveShadowRadius > 0.f; };
-
-    // Returns the transform hint set by Window Manager on the layer or one of its parents.
-    // This traverses the current state because the data is needed when creating
-    // the layer(off drawing thread) and the hint should be available before the producer
-    // is ready to acquire a buffer.
-    ui::Transform::RotationFlags getFixedTransformHint() const;
-
-    /**
-     * Traverse this layer and it's hierarchy of children directly. Unlike traverseInZOrder
-     * which will not emit children who have relativeZOrder to another layer, this method
-     * just directly emits all children. It also emits them in no particular order.
-     * So this method is not suitable for graphical operations, as it doesn't represent
-     * the scene state, but it's also more efficient than traverseInZOrder and so useful for
-     * book-keeping.
-     */
-    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
-     * parent.
-     */
-    void traverseChildrenInZOrder(LayerVector::StateSet, const LayerVector::Visitor&);
-
-    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
-    // the layer dtor so as not to dangle layers.  Since the layer has not
-    // committed its transaction when the layer is destroyed, we must add
-    // current children.  This is safe in the dtor as we will no longer update
-    // the current state, but should not be called anywhere else!
-    LayerVector& getCurrentChildren() { return mCurrentChildren; }
-
-    void addChild(const sp<Layer>&);
-    // Returns index if removed, or negative value otherwise
-    // for symmetry with Vector::remove
-    ssize_t removeChild(const sp<Layer>& layer);
-    sp<Layer> getParent() const { return mCurrentParent.promote(); }
-
-    // Should be called with the surfaceflinger statelock held
-    bool isAtRoot() const { return mIsAtRoot; }
-    void setIsAtRoot(bool isAtRoot) { mIsAtRoot = isAtRoot; }
-
-    bool hasParent() const { return getParent() != nullptr; }
-    Rect getScreenBounds(bool reduceTransparentRegion = true) const;
-    bool setChildLayer(const sp<Layer>& childLayer, int32_t z);
-    bool setChildRelativeLayer(const sp<Layer>& childLayer,
-            const sp<IBinder>& relativeToHandle, int32_t relativeZ);
-
-    // Copy the current list of children to the drawing state. Called by
-    // SurfaceFlinger to complete a transaction.
-    void commitChildList();
-    int32_t getZ(LayerVector::StateSet) const;
-
     /**
      * Returns the cropped buffer size or the layer crop if the layer has no buffer. Return
      * INVALID_RECT if the layer has no buffer and no crop.
@@ -784,15 +325,10 @@
      */
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
-    bool setFrameRate(FrameRate::FrameRateVote);
-    bool setFrameRateCategory(FrameRateCategory, bool smoothSwitchOnly);
-
-    bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
-
-    virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
-    void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
+    void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime,
+                                                   gui::GameMode gameMode);
     void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
-                                                       nsecs_t postTime);
+                                                       nsecs_t postTime, gui::GameMode gameMode);
 
     void addSurfaceFrameDroppedForBuffer(std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame,
                                          nsecs_t dropTime);
@@ -801,60 +337,25 @@
             nsecs_t currentLatchTime);
 
     std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForTransaction(
-            const FrameTimelineInfo& info, nsecs_t postTime);
+            const FrameTimelineInfo& info, nsecs_t postTime, gui::GameMode gameMode);
     std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForBuffer(
-            const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName);
+            const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName,
+            gui::GameMode gameMode);
     void setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
-                                               std::string debugName);
+                                               std::string debugName, gui::GameMode gameMode);
 
     bool setTrustedPresentationInfo(TrustedPresentationThresholds const& thresholds,
                                     TrustedPresentationListener const& listener);
+    void setBufferReleaseChannel(
+            const std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint>& channel);
 
     // Creates a new handle each time, so we only expect
     // this to be called once.
     sp<IBinder> getHandle();
     const std::string& getName() const { return mName; }
-    bool getPremultipledAlpha() const;
-    void setInputInfo(const gui::WindowInfo& info);
-
-    struct InputDisplayArgs {
-        const ui::Transform* transform = nullptr;
-        bool isSecure = false;
-    };
-    gui::WindowInfo fillInputInfo(const InputDisplayArgs& displayArgs);
-
-    /**
-     * Returns whether this layer has an explicitly set input-info.
-     */
-    bool hasInputInfo() const;
-
-    // Sets the gui::GameMode for the tree rooted at this layer. A layer in the tree inherits this
-    // gui::GameMode unless it (or an ancestor) has GAME_MODE_METADATA.
-    void setGameModeForTree(gui::GameMode);
-
-    void setGameMode(gui::GameMode gameMode) { mGameMode = gameMode; }
-    gui::GameMode getGameMode() const { return mGameMode; }
 
     virtual uid_t getOwnerUid() const { return mOwnerUid; }
 
-    pid_t getOwnerPid() { return mOwnerPid; }
-
-    int32_t getOwnerAppId() { return mOwnerAppId; }
-
-    // This layer is not a clone, but it's the parent to the cloned hierarchy. The
-    // variable mClonedChild represents the top layer that will be cloned so this
-    // layer will be the parent of mClonedChild.
-    // The layers in the cloned hierarchy will match the lifetime of the real layers. That is
-    // if the real layer is destroyed, then the clone layer will also be destroyed.
-    sp<Layer> mClonedChild;
-    bool mHadClonedChild = false;
-    void setClonedChild(const sp<Layer>& mClonedChild);
-
-    mutable bool contentDirty{false};
-    Region surfaceDamageRegion;
-
-    // True when the surfaceDamageRegion is recognized as a small area update.
-    bool mSmallDirty{false};
     // Used to check if mUsedVsyncIdForRefreshRateSelection should be expired when it stop updating.
     nsecs_t mMaxTimeForUseVsyncId = 0;
     // True when DrawState.useVsyncIdForRefreshRateSelection previously set to true during updating
@@ -866,65 +367,16 @@
     // the same.
     const int32_t sequence;
 
-    bool mPendingHWCDestroy{false};
-
-    bool backpressureEnabled() const {
-        return mDrawingState.flags & layer_state_t::eEnableBackpressure;
-    }
-
-    bool setStretchEffect(const StretchEffect& effect);
-    StretchEffect getStretchEffect() const;
-    bool enableBorder(bool shouldEnable, float width, const half4& color);
-    bool isBorderEnabled();
-    float getBorderWidth();
-    const half4& getBorderColor();
-
-    bool setBufferCrop(const Rect& /* bufferCrop */);
-    bool setDestinationFrame(const Rect& /* destinationFrame */);
     // See mPendingBufferTransactions
     void decrementPendingBufferCount();
     std::atomic<int32_t>* getPendingBufferCounter() { return &mPendingBufferTransactions; }
     std::string getPendingBufferCounterName() { return mBlastTransactionName; }
-    bool updateGeometry();
-
-    bool isSimpleBufferUpdate(const layer_state_t& s) const;
-
-    static bool isOpaqueFormat(PixelFormat format);
-
-    // Updates the LayerSnapshot. This must be called prior to sending layer data to
-    // CompositionEngine or RenderEngine (i.e. before calling CompositionEngine::present or
-    // LayerFE::prepareClientComposition).
-    //
-    // TODO(b/238781169) Remove direct calls to RenderEngine::drawLayers that don't go through
-    // CompositionEngine to create a single path for composing layers.
-    void updateSnapshot(bool updateGeometry);
-    void updateChildrenSnapshots(bool updateGeometry);
-    void updateMetadataSnapshot(const LayerMetadata& parentMetadata);
-    void updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
-                                        std::unordered_set<Layer*>& visited);
-    sp<Layer> getClonedFrom() const {
-        return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr;
-    }
-    bool isClone() { return mClonedFrom != nullptr; }
-
-    bool willPresentCurrentTransaction() const;
-
     void callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
                                    const sp<GraphicBuffer>& buffer, uint64_t framenumber,
                                    const sp<Fence>& releaseFence);
-    bool setFrameRateForLayerTreeLegacy(FrameRate, nsecs_t now);
     bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&, nsecs_t now);
     void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&, nsecs_t now);
     void recordLayerHistoryAnimationTx(const scheduler::LayerProps&, nsecs_t now);
-    auto getLayerProps() const {
-        return scheduler::LayerProps{.visible = isVisible(),
-                                     .bounds = getBounds(),
-                                     .transform = getTransform(),
-                                     .setFrameRateVote = getFrameRateForLayerTree(),
-                                     .frameRateSelectionPriority = getFrameRateSelectionPriority(),
-                                     .isSmallDirty = mSmallDirty,
-                                     .isFrontBuffered = isFrontBuffered()};
-    };
     bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
     void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
         mTransformHint = transformHint;
@@ -934,6 +386,7 @@
     // the release fences from the correct displays when we release the last buffer
     // from the layer.
     std::vector<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+
     struct FenceAndContinuation {
         ftl::SharedFuture<FenceResult> future;
         std::function<FenceResult(FenceResult)> continuation;
@@ -946,12 +399,23 @@
             }
         }
     };
-    std::vector<FenceAndContinuation> mAdditionalPreviousReleaseFences;
+    std::vector<FenceAndContinuation> mPreviousReleaseFenceAndContinuations;
+
+    // Release fences for buffers that have not yet received a release
+    // callback. A release callback may not be given when capturing
+    // screenshots asynchronously. There may be no buffer update for the
+    // layer, but the layer will still be composited on the screen in every
+    // frame. Kepping track of these fences ensures that they are not dropped
+    // and can be dispatched to the client at a later time. Older fences are
+    // dropped when a layer stack receives a new fence.
+    // TODO(b/300533018): Track fence per multi-instance RenderEngine
+    ftl::SmallMap<ui::LayerStack, ftl::Future<FenceResult>, ui::kDisplayCapacity>
+            mAdditionalPreviousReleaseFences;
+
     // Exposed so SurfaceFlinger can assert that it's held
     const sp<SurfaceFlinger> mFlinger;
 
     // Check if the damage region is a small dirty.
-    void setIsSmallDirty(const Region& damageRegion, const ui::Transform& layerToDisplayTransform);
     void setIsSmallDirty(frontend::LayerSnapshot* snapshot);
 
 protected:
@@ -963,64 +427,16 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId);
-    void preparePerFrameCompositionState();
-    void preparePerFrameBufferCompositionState();
-    void preparePerFrameEffectsCompositionState();
     void gatherBufferInfo();
-    void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
 
-    bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
-
-    void cloneDrawingState(const Layer* from);
-    void updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
-    void updateClonedChildren(const sp<Layer>& mirrorRoot,
-                              std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
-    void updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
-    void addChildToDrawing(const sp<Layer>&);
-    void updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap);
-
-    void prepareBasicGeometryCompositionState();
-    void prepareGeometryCompositionState();
-    void prepareCursorCompositionState();
-
-    uint32_t getEffectiveUsage(uint32_t usage) const;
-
-    /**
-     * Setup rounded corners coordinates of this layer, taking into account the layer bounds and
-     * crop coordinates, transforming them into layer space.
-     */
-    void setupRoundedCornersCropCoordinates(Rect win, const FloatRect& roundedCornersCrop) const;
-    void setParent(const sp<Layer>&);
-    LayerVector makeTraversalList(LayerVector::StateSet, bool* outSkipRelativeZUsers);
-    void addZOrderRelative(const wp<Layer>& relative);
-    void removeZOrderRelative(const wp<Layer>& relative);
     compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const;
     compositionengine::OutputLayer* findOutputLayerForDisplay(
             const DisplayDevice*, const frontend::LayerHierarchy::TraversalPath& path) const;
-    bool usingRelativeZ(LayerVector::StateSet) const;
 
-    virtual ui::Transform getInputTransform() const;
-    /**
-     * Get the bounds in layer space within which this layer can receive input.
-     *
-     * These bounds are used to:
-     * - Determine the input frame for the layer to be used for occlusion detection; and
-     * - Determine the coordinate space within which the layer will receive input. The top-left of
-     *   this rect will be the origin of the coordinate space that the input events sent to the
-     *   layer will be in (prior to accounting for surface insets).
-     *
-     * The layer can still receive touch input if these bounds are invalid if
-     * "replaceTouchableRegionWithCrop" is specified. In this case, the layer will receive input
-     * in this layer's space, regardless of the specified crop layer.
-     */
-    std::pair<FloatRect, bool> getInputBounds(bool fillParentBounds) const;
-
-    bool mPremultipliedAlpha{true};
     const std::string mName;
     const std::string mTransactionName{"TX - " + mName};
 
-    // These are only accessed by the main thread or the tracing thread.
+    // These are only accessed by the main thread.
     State mDrawingState;
 
     TrustedPresentationThresholds mTrustedPresentationThresholds;
@@ -1030,44 +446,22 @@
     int64_t mEnteredTrustedPresentationStateTime = -1;
 
     uint32_t mTransactionFlags{0};
-    // Updated in doTransaction, used to track the last sequence number we
-    // committed. Currently this is really only used for updating visible
-    // regions.
-    int32_t mLastCommittedTxSequence = -1;
 
     // Timestamp history for UIAutomation. Thread safe.
     FrameTracker mFrameTracker;
 
     // main thread
     sp<NativeHandle> mSidebandStream;
-    // False if the buffer and its contents have been previously used for GPU
-    // composition, true otherwise.
-    bool mIsActiveBufferUpdatedForGpu = true;
 
     // We encode unset as -1.
     std::atomic<uint64_t> mCurrentFrameNumber{0};
-    // Whether filtering is needed b/c of the drawingstate
-    bool mNeedsFiltering{false};
-
-    std::atomic<bool> mRemovedFromDrawingState{false};
-
-    // page-flip thread (currently main thread)
-    bool mProtectedByApp{false}; // application requires protected path to external sink
 
     // protected by mLock
     mutable Mutex mLock;
 
-    const wp<Client> mClientRef;
-
     // This layer can be a cursor on some displays.
     bool mPotentialCursor{false};
 
-    LayerVector mCurrentChildren{LayerVector::StateSet::Current};
-    LayerVector mDrawingChildren{LayerVector::StateSet::Drawing};
-
-    wp<Layer> mCurrentParent;
-    wp<Layer> mDrawingParent;
-
     // Window types from WindowManager.LayoutParams
     const gui::WindowInfo::Type mWindowType;
 
@@ -1085,8 +479,6 @@
     // Used in buffer stuffing analysis in FrameTimeline.
     nsecs_t mLastLatchTime = 0;
 
-    mutable bool mDrawingStateModified = false;
-
     sp<Fence> mLastClientCompositionFence;
     bool mClearClientCompositionFenceOnLayerDisplayed = false;
 private:
@@ -1098,62 +490,20 @@
     friend class TransactionFrameTracerTest;
     friend class TransactionSurfaceFrameTest;
 
-    bool getAutoRefresh() const { return mDrawingState.autoRefresh; }
     bool getSidebandStreamChanged() const { return mSidebandStreamChanged; }
 
     std::atomic<bool> mSidebandStreamChanged{false};
 
-    // Returns true if the layer can draw shadows on its border.
-    virtual bool canDrawShadows() const { return true; }
-
     aidl::android::hardware::graphics::composer3::Composition getCompositionType(
             const DisplayDevice&) const;
     aidl::android::hardware::graphics::composer3::Composition getCompositionType(
             const compositionengine::OutputLayer*) const;
-    /**
-     * Returns an unsorted vector of all layers that are part of this tree.
-     * That includes the current layer and all its descendants.
-     */
-    std::vector<Layer*> getLayersInTree(LayerVector::StateSet);
-    /**
-     * Traverses layers that are part of this tree in the correct z order.
-     * layersInTree must be sorted before calling this method.
-     */
-    void traverseChildrenInZOrderInner(const std::vector<Layer*>& layersInTree,
-                                       LayerVector::StateSet, const LayerVector::Visitor&);
-    LayerVector makeChildrenTraversalList(LayerVector::StateSet,
-                                          const std::vector<Layer*>& layersInTree);
-
-    void updateTreeHasFrameRateVote();
-    bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
-                                        bool* transactionNeeded);
-    void setZOrderRelativeOf(const wp<Layer>& relativeOf);
-    bool isTrustedOverlay() const;
-    gui::DropInputMode getDropInputMode() const;
-    void handleDropInputMode(gui::WindowInfo& info) const;
-
-    // Find the root of the cloned hierarchy, this means the first non cloned parent.
-    // This will return null if first non cloned parent is not found.
-    sp<Layer> getClonedRoot();
-
-    // Finds the top most layer in the hierarchy. This will find the root Layer where the parent is
-    // null.
-    sp<Layer> getRootLayer();
-
-    // Fills in the touch occlusion mode of the first parent (including this layer) that
-    // hasInputInfo() or no-op if no such parent is found.
-    void fillTouchOcclusionMode(gui::WindowInfo& info);
-
-    // Fills in the frame and transform info for the gui::WindowInfo.
-    void fillInputFrameInfo(gui::WindowInfo&, const ui::Transform& screenToDisplay);
 
     inline void tracePendingBufferCount(int32_t pendingBuffers);
 
     // Latch sideband stream and returns true if the dirty region should be updated.
     bool latchSidebandStream(bool& recomputeVisibleRegions);
 
-    bool hasFrameUpdate() const;
-
     void updateTexImage(nsecs_t latchTime, bool bgColorOnly = false);
 
     // Crop that applies to the buffer
@@ -1164,15 +514,6 @@
                                    const sp<Fence>& releaseFence,
                                    uint32_t currentMaxAcquiredBufferCount);
 
-    // Returns true if the transformed buffer size does not match the layer size and we need
-    // to apply filtering.
-    bool bufferNeedsFiltering() const;
-
-    // Returns true if there is a valid color to fill.
-    bool fillsColor() const;
-    // Returns true if this layer has a blur value.
-    bool hasBlur() const;
-    bool hasEffect() const { return fillsColor() || drawShadows() || hasBlur(); }
     bool hasBufferOrSidebandStream() const {
         return ((mSidebandStream != nullptr) || (mBufferInfo.mBuffer != nullptr));
     }
@@ -1181,46 +522,8 @@
         return ((mDrawingState.sidebandStream != nullptr) || (mDrawingState.buffer != nullptr));
     }
 
-    bool hasSomethingToDraw() const { return hasEffect() || hasBufferOrSidebandStream(); }
-
-    // Fills the provided vector with the currently available JankData and removes the processed
-    // JankData from the pending list.
-    void transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
-                                   std::vector<JankData>& jankData);
-
-    bool shouldOverrideChildrenFrameRate() const {
-        return getDrawingState().frameRateSelectionStrategy ==
-                FrameRateSelectionStrategy::OverrideChildren;
-    }
-
-    bool shouldPropagateFrameRate() const {
-        return getDrawingState().frameRateSelectionStrategy != FrameRateSelectionStrategy::Self;
-    }
-
-    // Cached properties computed from drawing state
-    // Effective transform taking into account parent transforms and any parent scaling, which is
-    // a transform from the current layer coordinate space to display(screen) coordinate space.
-    ui::Transform mEffectiveTransform;
-
-    // Bounds of the layer before any transformation is applied and before it has been cropped
-    // by its parents.
-    FloatRect mSourceBounds;
-
-    // Bounds of the layer in layer space. This is the mSourceBounds cropped by its layer crop and
-    // its parent bounds.
-    FloatRect mBounds;
-
-    // Layer bounds in screen space.
-    FloatRect mScreenBounds;
-
     bool mGetHandleCalled = false;
 
-    // The current layer is a clone of mClonedFrom. This means that this layer will update it's
-    // properties based on mClonedFrom. When mClonedFrom latches a new buffer for BufferLayers,
-    // this layer will update it's buffer. When mClonedFrom updates it's drawing state, children,
-    // and relatives, this layer will update as well.
-    wp<Layer> mClonedFrom;
-
     // The inherited shadow radius after taking into account the layer hierarchy. This is the
     // final shadow radius for this layer. If a shadow is specified for a layer, then effective
     // shadow radius is the set shadow radius, otherwise its the parent's shadow radius.
@@ -1229,27 +532,15 @@
     // Game mode for the layer. Set by WindowManagerShell and recorded by SurfaceFlingerStats.
     gui::GameMode mGameMode = gui::GameMode::Unsupported;
 
-    // A list of regions on this layer that should have blurs.
-    const std::vector<BlurRegion> getBlurRegions() const;
-
     bool mIsAtRoot = false;
 
     uint32_t mLayerCreationFlags;
 
-    bool findInHierarchy(const sp<Layer>&);
-
-    bool mBorderEnabled = false;
-    float mBorderWidth;
-    half4 mBorderColor;
-
-    void setTransformHintLegacy(ui::Transform::RotationFlags);
     void releasePreviousBuffer();
     void resetDrawingStateBufferInfo();
 
     // Transform hint provided to the producer. This must be accessed holding
     // the mStateLock.
-    ui::Transform::RotationFlags mTransformHintLegacy = ui::Transform::ROT_0;
-    bool mSkipReportingTransformHint = true;
     std::optional<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
 
     ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
@@ -1261,30 +552,23 @@
     // time.
     std::variant<nsecs_t, sp<Fence>> mCallbackHandleAcquireTimeOrFence = -1;
 
-    std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> mPendingJankClassifications;
-    // An upper bound on the number of SurfaceFrames in the pending classifications deque.
-    static constexpr int kPendingClassificationMaxSurfaceFrames = 50;
-
     const std::string mBlastTransactionName{"BufferTX - " + mName};
     // This integer is incremented everytime a buffer arrives at the server for this layer,
     // and decremented when a buffer is dropped or latched. When changed the integer is exported
-    // to systrace with ATRACE_INT and mBlastTransactionName. This way when debugging perf it is
+    // to systrace with SFTRACE_INT and mBlastTransactionName. This way when debugging perf it is
     // possible to see when a buffer arrived at the server, and in which frame it latched.
     //
     // You can understand the trace this way:
     //     - If the integer increases, a buffer arrived at the server.
     //     - If the integer decreases in latchBuffer, that buffer was latched
-    //     - If the integer decreases in setBuffer or doTransaction, a buffer was dropped
+    //     - If the integer decreases in setBuffer, a buffer was dropped
     std::atomic<int32_t> mPendingBufferTransactions{0};
 
     // Contains requested position and matrix updates. This will be applied if the client does
     // not specify a destination frame.
     ui::Transform mRequestedTransform;
 
-    sp<LayerFE> mLegacyLayerFE;
     std::vector<std::pair<frontend::LayerHierarchy::TraversalPath, sp<LayerFE>>> mLayerFEs;
-    std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
-            std::make_unique<frontend::LayerSnapshot>();
     bool mHandleAlive = false;
 };
 
diff --git a/services/surfaceflinger/LayerFE.cpp b/services/surfaceflinger/LayerFE.cpp
index 2dbcb84..b05f0ee 100644
--- a/services/surfaceflinger/LayerFE.cpp
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -19,14 +19,16 @@
 #define LOG_TAG "SurfaceFlinger"
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
+#include <common/trace.h>
 #include <gui/GLConsumer.h>
-#include <gui/TraceUtils.h>
 #include <math/vec3.h>
 #include <system/window.h>
-#include <utils/Log.h>
 
 #include "LayerFE.h"
 #include "SurfaceFlinger.h"
+#include "common/FlagManager.h"
+#include "ui/FenceResult.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -78,12 +80,21 @@
 
 LayerFE::LayerFE(const std::string& name) : mName(name) {}
 
+LayerFE::~LayerFE() {
+    // Ensures that no promise is left unfulfilled before the LayerFE is destroyed.
+    // An unfulfilled promise could occur when a screenshot is attempted, but the
+    // render area is invalid and there is no memory for the capture result.
+    if (FlagManager::getInstance().ce_fence_promise() &&
+        mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+        setReleaseFence(Fence::NO_FENCE);
+    }
+}
+
 const compositionengine::LayerFECompositionState* LayerFE::getCompositionState() const {
     return mSnapshot.get();
 }
 
-bool LayerFE::onPreComposition(nsecs_t refreshStartTime, bool) {
-    mCompositionResult.refreshStartTime = refreshStartTime;
+bool LayerFE::onPreComposition(bool) {
     return mSnapshot->hasReadyFrame;
 }
 
@@ -110,7 +121,7 @@
 
 std::optional<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientCompositionInternal(
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     compositionengine::LayerFE::LayerSettings layerSettings;
     layerSettings.geometry.boundaries =
             reduce(mSnapshot->geomLayerBounds, mSnapshot->transparentRegionHint);
@@ -162,6 +173,7 @@
             break;
     }
     layerSettings.stretchEffect = mSnapshot->stretchEffect;
+    layerSettings.edgeExtensionEffect = mSnapshot->edgeExtensionEffect;
     // Record the name of the layer for debugging further down the stack.
     layerSettings.name = mSnapshot->name;
 
@@ -202,7 +214,7 @@
 void LayerFE::prepareBufferStateClientComposition(
         compositionengine::LayerFE::LayerSettings& layerSettings,
         compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (CC_UNLIKELY(!mSnapshot->externalTexture)) {
         // If there is no buffer for the layer or we have sidebandstream where there is no
         // activeBuffer, then we need to return LayerSettings.
@@ -388,4 +400,29 @@
     return mSnapshot->externalTexture ? mSnapshot->externalTexture->getBuffer() : nullptr;
 }
 
+void LayerFE::setReleaseFence(const FenceResult& releaseFence) {
+    // Promises should not be fulfilled more than once. This case can occur if virtual
+    // displays with the same layerstack ID are being created and destroyed in quick
+    // succession, such as in tests. This would result in a race condition in which
+    // multiple displays have the same layerstack ID within the same vsync interval.
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::FULFILLED) {
+        return;
+    }
+    mReleaseFence.set_value(releaseFence);
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::FULFILLED;
+}
+
+// LayerFEs are reused and a new fence needs to be created whevever a buffer is latched.
+ftl::Future<FenceResult> LayerFE::createReleaseFenceFuture() {
+    if (mReleaseFencePromiseStatus == ReleaseFencePromiseStatus::INITIALIZED) {
+        LOG_ALWAYS_FATAL("Attempting to create a new promise while one is still unfulfilled.");
+    }
+    mReleaseFence = std::promise<FenceResult>();
+    mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::INITIALIZED;
+    return mReleaseFence.get_future();
+}
+
+LayerFE::ReleaseFencePromiseStatus LayerFE::getReleaseFencePromiseStatus() {
+    return mReleaseFencePromiseStatus;
+}
 } // namespace android
diff --git a/services/surfaceflinger/LayerFE.h b/services/surfaceflinger/LayerFE.h
index d584fb7..658f949 100644
--- a/services/surfaceflinger/LayerFE.h
+++ b/services/surfaceflinger/LayerFE.h
@@ -22,13 +22,13 @@
 #include "compositionengine/LayerFE.h"
 #include "compositionengine/LayerFECompositionState.h"
 #include "renderengine/LayerSettings.h"
+#include "ui/LayerStack.h"
+
+#include <ftl/future.h>
 
 namespace android {
 
 struct CompositionResult {
-    // TODO(b/238781169) update CE to no longer pass refreshStartTime to LayerFE::onPreComposition
-    // and remove this field.
-    nsecs_t refreshStartTime = 0;
     std::vector<std::pair<ftl::SharedFuture<FenceResult>, ui::LayerStack>> releaseFences;
     sp<Fence> lastClientCompositionFence = nullptr;
 };
@@ -36,10 +36,11 @@
 class LayerFE : public virtual RefBase, public virtual compositionengine::LayerFE {
 public:
     LayerFE(const std::string& name);
+    virtual ~LayerFE();
 
     // compositionengine::LayerFE overrides
     const compositionengine::LayerFECompositionState* getCompositionState() const override;
-    bool onPreComposition(nsecs_t refreshStartTime, bool updatingOutputGeometryThisFrame) override;
+    bool onPreComposition(bool updatingOutputGeometryThisFrame) override;
     void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack) override;
     const char* getDebugName() const override;
     int32_t getSequence() const override;
@@ -50,6 +51,9 @@
     std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
             compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
     CompositionResult&& stealCompositionResult();
+    ftl::Future<FenceResult> createReleaseFenceFuture() override;
+    void setReleaseFence(const FenceResult& releaseFence) override;
+    LayerFE::ReleaseFencePromiseStatus getReleaseFencePromiseStatus() override;
 
     std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
 
@@ -79,6 +83,8 @@
 
     CompositionResult mCompositionResult;
     std::string mName;
+    std::promise<FenceResult> mReleaseFence;
+    ReleaseFencePromiseStatus mReleaseFencePromiseStatus = ReleaseFencePromiseStatus::UNINITIALIZED;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index aa6026e..5eea45b 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -178,7 +178,7 @@
 }
 
 void LayerProtoHelper::writeToProto(
-        const WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
+        const WindowInfo& inputInfo,
         std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto) {
     if (inputInfo.token == nullptr) {
         return;
@@ -208,13 +208,6 @@
     proto->set_global_scale_factor(inputInfo.globalScaleFactor);
     LayerProtoHelper::writeToProtoDeprecated(inputInfo.transform, proto->mutable_transform());
     proto->set_replace_touchable_region_with_crop(inputInfo.replaceTouchableRegionWithCrop);
-    auto cropLayer = touchableRegionBounds.promote();
-    if (cropLayer != nullptr) {
-        proto->set_crop_layer_id(cropLayer->sequence);
-        LayerProtoHelper::writeToProto(cropLayer->getScreenBounds(
-                                               false /* reduceTransparentRegion */),
-                                       [&]() { return proto->mutable_touchable_region_crop(); });
-    }
 }
 
 void LayerProtoHelper::writeToProto(const mat4 matrix,
@@ -263,9 +256,10 @@
     outRegion.bottom = proto.bottom();
 }
 
-perfetto::protos::LayersProto LayerProtoFromSnapshotGenerator::generate(
+LayerProtoFromSnapshotGenerator& LayerProtoFromSnapshotGenerator::with(
         const frontend::LayerHierarchy& root) {
     mLayersProto.clear_layers();
+    mVisitedLayers.clear();
     std::unordered_set<uint64_t> stackIdsToSkip;
     if ((mTraceFlags & LayerTracing::TRACE_VIRTUAL_DISPLAYS) == 0) {
         for (const auto& [layerStack, displayInfo] : mDisplayInfos) {
@@ -304,9 +298,40 @@
         }
     }
 
-    mDefaultSnapshots.clear();
-    mChildToRelativeParent.clear();
-    return std::move(mLayersProto);
+    return *this;
+}
+
+LayerProtoFromSnapshotGenerator& LayerProtoFromSnapshotGenerator::withOffscreenLayers(
+        const frontend::LayerHierarchy& offscreenRoot) {
+    // Add a fake invisible root layer to the proto output and parent all the offscreen layers to
+    // it.
+    perfetto::protos::LayerProto* rootProto = mLayersProto.add_layers();
+    const int32_t offscreenRootLayerId = INT32_MAX - 2;
+    rootProto->set_id(offscreenRootLayerId);
+    rootProto->set_name("Offscreen Root");
+    rootProto->set_parent(-1);
+
+    perfetto::protos::LayersProto offscreenLayers =
+            LayerProtoFromSnapshotGenerator(mSnapshotBuilder, mDisplayInfos, mLegacyLayers,
+                                            mTraceFlags)
+                    .with(offscreenRoot)
+                    .generate();
+
+    for (int i = 0; i < offscreenLayers.layers_size(); i++) {
+        perfetto::protos::LayerProto* layerProto = offscreenLayers.mutable_layers()->Mutable(i);
+        if (layerProto->parent() == -1) {
+            layerProto->set_parent(offscreenRootLayerId);
+            // Add layer as child of the fake root
+            rootProto->add_children(layerProto->id());
+        }
+    }
+
+    mLayersProto.mutable_layers()->Reserve(mLayersProto.layers_size() +
+                                           offscreenLayers.layers_size());
+    std::copy(offscreenLayers.layers().begin(), offscreenLayers.layers().end(),
+              RepeatedFieldBackInserter(mLayersProto.mutable_layers()));
+
+    return *this;
 }
 
 frontend::LayerSnapshot* LayerProtoFromSnapshotGenerator::getSnapshot(
@@ -326,6 +351,11 @@
     perfetto::protos::LayerProto* layerProto = mLayersProto.add_layers();
     const frontend::RequestedLayerState& layer = *root.getLayer();
     frontend::LayerSnapshot* snapshot = getSnapshot(path, layer);
+    if (mVisitedLayers.find(snapshot->uniqueSequence) != mVisitedLayers.end()) {
+        TransactionTraceWriter::getInstance().invoke("DuplicateLayer", /* overwrite= */ false);
+        return;
+    }
+    mVisitedLayers.insert(snapshot->uniqueSequence);
     LayerProtoHelper::writeSnapshotToProto(layerProto, layer, *snapshot, mTraceFlags);
 
     for (const auto& [child, variant] : root.mChildren) {
@@ -334,7 +364,7 @@
                                                                           variant);
         frontend::LayerSnapshot* childSnapshot = getSnapshot(path, layer);
         if (variant == Variant::Attached || variant == Variant::Detached ||
-            variant == Variant::Mirror) {
+            frontend::LayerHierarchy::isMirror(variant)) {
             mChildToParent[childSnapshot->uniqueSequence] = snapshot->uniqueSequence;
             layerProto->add_children(childSnapshot->uniqueSequence);
         } else if (variant == Variant::Relative) {
@@ -382,7 +412,8 @@
     layerInfo->set_corner_radius(
             (snapshot.roundedCorner.radius.x + snapshot.roundedCorner.radius.y) / 2.0);
     layerInfo->set_background_blur_radius(snapshot.backgroundBlurRadius);
-    layerInfo->set_is_trusted_overlay(snapshot.isTrustedOverlay);
+    layerInfo->set_is_trusted_overlay(snapshot.trustedOverlay == gui::TrustedOverlay::ENABLED);
+    // TODO(b/339701674) update protos
     LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
     LayerProtoHelper::writePositionToProto(transform.tx(), transform.ty(),
                                            [&]() { return layerInfo->mutable_position(); });
@@ -444,7 +475,7 @@
     layerInfo->set_owner_uid(requestedState.ownerUid.val());
 
     if ((traceFlags & LayerTracing::TRACE_INPUT) && snapshot.hasInputInfo()) {
-        LayerProtoHelper::writeToProto(snapshot.inputInfo, {},
+        LayerProtoHelper::writeToProto(snapshot.inputInfo,
                                        [&]() { return layerInfo->mutable_input_window_info(); });
     }
 
@@ -465,7 +496,7 @@
     displays.Reserve(displayInfos.size());
     for (const auto& [layerStack, displayInfo] : displayInfos) {
         auto displayProto = displays.Add();
-        displayProto->set_id(displayInfo.info.displayId);
+        displayProto->set_id(displayInfo.info.displayId.val());
         displayProto->set_layer_stack(layerStack.id);
         displayProto->mutable_size()->set_w(displayInfo.info.logicalWidth);
         displayProto->mutable_size()->set_h(displayInfo.info.logicalHeight);
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index 20c2260..41ea684 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -62,7 +62,7 @@
             const renderengine::ExternalTexture& buffer,
             std::function<perfetto::protos::ActiveBufferProto*()> getActiveBufferProto);
     static void writeToProto(
-            const gui::WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
+            const gui::WindowInfo& inputInfo,
             std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto);
     static void writeToProto(const mat4 matrix,
                              perfetto::protos::ColorTransformProto* colorTransformProto);
@@ -88,7 +88,12 @@
             mLegacyLayers(legacyLayers),
             mDisplayInfos(displayInfos),
             mTraceFlags(traceFlags) {}
-    perfetto::protos::LayersProto generate(const frontend::LayerHierarchy& root);
+    LayerProtoFromSnapshotGenerator& with(const frontend::LayerHierarchy& root);
+    // Creates a fake root and adds all offscreen layers from the passed in hierarchy to the fake
+    // root
+    LayerProtoFromSnapshotGenerator& withOffscreenLayers(
+            const frontend::LayerHierarchy& offscreenRoot);
+    perfetto::protos::LayersProto generate() { return mLayersProto; };
 
 private:
     void writeHierarchyToProto(const frontend::LayerHierarchy& root,
@@ -101,6 +106,8 @@
     const frontend::DisplayInfos& mDisplayInfos;
     uint32_t mTraceFlags;
     perfetto::protos::LayersProto mLayersProto;
+    std::unordered_set<uint32_t> mVisitedLayers;
+
     // winscope expects all the layers, so provide a snapshot even if it not currently drawing
     std::unordered_map<frontend::LayerHierarchy::TraversalPath, frontend::LayerSnapshot,
                        frontend::LayerHierarchy::TraversalPathHash>
diff --git a/services/surfaceflinger/LayerRenderArea.cpp b/services/surfaceflinger/LayerRenderArea.cpp
index 51d4ff8..bfe6d2a 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -24,38 +24,24 @@
 #include "SurfaceFlinger.h"
 
 namespace android {
-namespace {
 
-void reparentForDrawing(const sp<Layer>& oldParent, const sp<Layer>& newParent,
-                   const Rect& drawingBounds) {
-        // Compute and cache the bounds for the new parent layer.
-        newParent->computeBounds(drawingBounds.toFloatRect(), ui::Transform(),
-            0.f /* shadowRadius */);
-        newParent->updateSnapshot(true /* updateGeometry */);
-        oldParent->setChildrenDrawingParent(newParent);
-};
-
-} // namespace
-
-LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop,
-                                 ui::Size reqSize, ui::Dataspace reqDataSpace, bool childrenOnly,
-                                 bool allowSecureLayers, const ui::Transform& layerTransform,
-                                 const Rect& layerBufferSize, bool hintForSeamlessTransition)
-      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, hintForSeamlessTransition,
-                   allowSecureLayers),
+LayerRenderArea::LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot,
+                                 const Rect& crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                                 const ui::Transform& layerTransform, const Rect& layerBufferSize,
+                                 ftl::Flags<RenderArea::Options> options)
+      : RenderArea(reqSize, CaptureFill::CLEAR, reqDataSpace, options),
         mLayer(std::move(layer)),
-        mLayerTransform(layerTransform),
+        mLayerSnapshot(std::move(layerSnapshot)),
         mLayerBufferSize(layerBufferSize),
         mCrop(crop),
-        mFlinger(flinger),
-        mChildrenOnly(childrenOnly) {}
+        mTransform(layerTransform) {}
 
 const ui::Transform& LayerRenderArea::getTransform() const {
     return mTransform;
 }
 
 bool LayerRenderArea::isSecure() const {
-    return mAllowSecureLayers;
+    return mOptions.test(Options::CAPTURE_SECURE_LAYERS);
 }
 
 sp<const DisplayDevice> LayerRenderArea::getDisplayDevice() const {
@@ -71,53 +57,4 @@
     }
 }
 
-void LayerRenderArea::render(std::function<void()> drawLayers) {
-    using namespace std::string_literals;
-
-    if (!mChildrenOnly) {
-        mTransform = mLayerTransform.inverse();
-    }
-
-    if (mFlinger.mLayerLifecycleManagerEnabled) {
-        drawLayers();
-        return;
-    }
-    // If layer is offscreen, update mirroring info if it exists
-    if (mLayer->isRemovedFromCurrentState()) {
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateMirrorInfo({}); });
-        mLayer->traverse(LayerVector::StateSet::Drawing,
-                         [&](Layer* layer) { layer->updateCloneBufferInfo(); });
-    }
-
-    if (!mChildrenOnly) {
-        // If the layer is offscreen, compute bounds since we don't compute bounds for offscreen
-        // layers in a regular cycles.
-        if (mLayer->isRemovedFromCurrentState()) {
-            FloatRect maxBounds = mFlinger.getMaxDisplayBounds();
-            mLayer->computeBounds(maxBounds, ui::Transform(), 0.f /* shadowRadius */);
-        }
-        drawLayers();
-    } else {
-        // In the "childrenOnly" case we reparent the children to a screenshot
-        // layer which has no properties set and which does not draw.
-        //  We hold the statelock as the reparent-for-drawing operation modifies the
-        //  hierarchy and there could be readers on Binder threads, like dump.
-        auto screenshotParentLayer = mFlinger.getFactory().createEffectLayer(
-                {&mFlinger, nullptr, "Screenshot Parent"s, ISurfaceComposerClient::eNoColorFill,
-                 LayerMetadata()});
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            reparentForDrawing(mLayer, screenshotParentLayer, getSourceCrop());
-        }
-        drawLayers();
-        {
-            Mutex::Autolock _l(mFlinger.mStateLock);
-            mLayer->setChildrenDrawingParent(mLayer);
-        }
-    }
-    mLayer->updateSnapshot(/*updateGeometry=*/true);
-    mLayer->updateChildrenSnapshots(/*updateGeometry=*/true);
-}
-
 } // namespace android
diff --git a/services/surfaceflinger/LayerRenderArea.h b/services/surfaceflinger/LayerRenderArea.h
index aa609ee..f72c7c7 100644
--- a/services/surfaceflinger/LayerRenderArea.h
+++ b/services/surfaceflinger/LayerRenderArea.h
@@ -32,29 +32,26 @@
 
 class LayerRenderArea : public RenderArea {
 public:
-    LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> layer, const Rect& crop, ui::Size reqSize,
-                    ui::Dataspace reqDataSpace, bool childrenOnly, bool allowSecureLayers,
+    LayerRenderArea(sp<Layer> layer, frontend::LayerSnapshot layerSnapshot, const Rect& crop,
+                    ui::Size reqSize, ui::Dataspace reqDataSpace,
                     const ui::Transform& layerTransform, const Rect& layerBufferSize,
-                    bool hintForSeamlessTransition);
+                    ftl::Flags<RenderArea::Options> options);
 
     const ui::Transform& getTransform() const override;
     bool isSecure() const override;
     sp<const DisplayDevice> getDisplayDevice() const override;
     Rect getSourceCrop() const override;
 
-    void render(std::function<void()> drawLayers) override;
-    virtual sp<Layer> getParentLayer() const { return mLayer; }
+    sp<Layer> getParentLayer() const override { return mLayer; }
+    const frontend::LayerSnapshot* getLayerSnapshot() const override { return &mLayerSnapshot; }
 
 private:
     const sp<Layer> mLayer;
-    const ui::Transform mLayerTransform;
+    const frontend::LayerSnapshot mLayerSnapshot;
     const Rect mLayerBufferSize;
     const Rect mCrop;
 
     ui::Transform mTransform;
-
-    SurfaceFlinger& mFlinger;
-    const bool mChildrenOnly;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/LayerVector.cpp b/services/surfaceflinger/LayerVector.cpp
index f52e60d..13e054e 100644
--- a/services/surfaceflinger/LayerVector.cpp
+++ b/services/surfaceflinger/LayerVector.cpp
@@ -45,51 +45,12 @@
     const auto& lState = l->getDrawingState();
     const auto& rState = r->getDrawingState();
 
-    const auto ls = lState.layerStack;
-    const auto rs = rState.layerStack;
-    if (ls != rs)
-        return (ls > rs) ? 1 : -1;
-
-    int32_t lz = lState.z;
-    int32_t rz = rState.z;
-    if (lz != rz)
-        return (lz > rz) ? 1 : -1;
-
     if (l->sequence == r->sequence)
         return 0;
 
     return (l->sequence > r->sequence) ? 1 : -1;
 }
 
-void LayerVector::traverseInZOrder(StateSet stateSet, const Visitor& visitor) const {
-    for (size_t i = 0; i < size(); i++) {
-        const auto& layer = (*this)[i];
-        auto& state = layer->getDrawingState();
-        if (state.isRelativeOf) {
-            continue;
-        }
-        layer->traverseInZOrder(stateSet, visitor);
-    }
-}
-
-void LayerVector::traverseInReverseZOrder(StateSet stateSet, const Visitor& visitor) const {
-    for (auto i = static_cast<int64_t>(size()) - 1; i >= 0; i--) {
-        const auto& layer = (*this)[i];
-        auto& state = layer->getDrawingState();
-        if (state.isRelativeOf) {
-            continue;
-        }
-        layer->traverseInReverseZOrder(stateSet, visitor);
-     }
-}
-
-void LayerVector::traverse(const Visitor& visitor) const {
-    for (auto i = static_cast<int64_t>(size()) - 1; i >= 0; i--) {
-        const auto& layer = (*this)[i];
-        layer->traverse(mStateSet, visitor);
-    }
-}
-
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/LayerVector.h b/services/surfaceflinger/LayerVector.h
index a531f4f..38dc11d 100644
--- a/services/surfaceflinger/LayerVector.h
+++ b/services/surfaceflinger/LayerVector.h
@@ -46,11 +46,8 @@
 
     // Sorts layer by layer-stack, Z order, and finally creation order (sequence).
     int do_compare(const void* lhs, const void* rhs) const override;
-
     using Visitor = std::function<void(Layer*)>;
-    void traverseInReverseZOrder(StateSet stateSet, const Visitor& visitor) const;
-    void traverseInZOrder(StateSet stateSet, const Visitor& visitor) const;
-    void traverse(const Visitor& visitor) const;
+
 private:
     const StateSet mStateSet;
 };
diff --git a/services/surfaceflinger/LocklessQueue.h b/services/surfaceflinger/LocklessQueue.h
index 6b63360..4d0b261 100644
--- a/services/surfaceflinger/LocklessQueue.h
+++ b/services/surfaceflinger/LocklessQueue.h
@@ -15,11 +15,11 @@
  */
 
 #pragma once
+
 #include <atomic>
 #include <optional>
 
-template <typename T>
-// Single consumer multi producer stack. We can understand the two operations independently to see
+// Single consumer multi producer queue. We can understand the two operations independently to see
 // why they are without race condition.
 //
 // push is responsible for maintaining a linked list stored in mPush, and called from multiple
@@ -36,33 +36,27 @@
 // then store the list and pop one element.
 //
 // If we already had something in the pop list we just pop directly.
+template <typename T>
 class LocklessQueue {
 public:
-    class Entry {
-    public:
-        T mValue;
-        std::atomic<Entry*> mNext;
-        Entry(T value) : mValue(value) {}
-    };
-    std::atomic<Entry*> mPush = nullptr;
-    std::atomic<Entry*> mPop = nullptr;
     bool isEmpty() { return (mPush.load() == nullptr) && (mPop.load() == nullptr); }
 
     void push(T value) {
-        Entry* entry = new Entry(value);
+        Entry* entry = new Entry(std::move(value));
         Entry* previousHead = mPush.load(/*std::memory_order_relaxed*/);
         do {
             entry->mNext = previousHead;
         } while (!mPush.compare_exchange_weak(previousHead, entry)); /*std::memory_order_release*/
     }
+
     std::optional<T> pop() {
         Entry* popped = mPop.load(/*std::memory_order_acquire*/);
         if (popped) {
             // Single consumer so this is fine
             mPop.store(popped->mNext /* , std::memory_order_release */);
-            auto value = popped->mValue;
+            auto value = std::move(popped->mValue);
             delete popped;
-            return std::move(value);
+            return value;
         } else {
             Entry* grabbedList = mPush.exchange(nullptr /* , std::memory_order_acquire */);
             if (!grabbedList) return std::nullopt;
@@ -74,9 +68,19 @@
                 grabbedList = next;
             }
             mPop.store(popped /* , std::memory_order_release */);
-            auto value = grabbedList->mValue;
+            auto value = std::move(grabbedList->mValue);
             delete grabbedList;
-            return std::move(value);
+            return value;
         }
     }
+
+private:
+    class Entry {
+    public:
+        T mValue;
+        std::atomic<Entry*> mNext;
+        Entry(T value) : mValue(value) {}
+    };
+    std::atomic<Entry*> mPush = nullptr;
+    std::atomic<Entry*> mPop = nullptr;
 };
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index ffc1dd7..13edd16 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -5,10 +5,12 @@
 domlaskowski@google.com
 jreck@google.com
 lpy@google.com
+mattbuckley@google.com
+melodymhsu@google.com
 pdwilliams@google.com
 racarr@google.com
 ramindani@google.com
 rnlee@google.com
 sallyqi@google.com
-scroggo@google.com
 vishnun@google.com
+xwxw@google.com
diff --git a/services/surfaceflinger/PowerAdvisor/OWNERS b/services/surfaceflinger/PowerAdvisor/OWNERS
new file mode 100644
index 0000000..9f40e27
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/ADPF_OWNERS
\ No newline at end of file
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index b960e33..35f12a0 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -28,10 +28,11 @@
 
 namespace android {
 
-auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color,
+auto RefreshRateOverlay::draw(int refreshRate, int renderFps, bool idle, SkColor color,
                               ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
         -> Buffers {
     const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
+    const bool isSetByHwc = features.test(Features::SetByHwc);
 
     Buffers buffers;
     buffers.reserve(loopCount);
@@ -71,7 +72,11 @@
         canvas->setMatrix(canvasTransform);
 
         int left = 0;
-        drawNumber(vsyncRate, left, color, *canvas);
+        if (idle && !isSetByHwc) {
+            drawDash(left, *canvas);
+        } else {
+            drawNumber(refreshRate, left, color, *canvas);
+        }
         left += 3 * (kDigitWidth + kDigitSpace);
         if (features.test(Features::Spinner)) {
             switch (i) {
@@ -104,7 +109,11 @@
         left += kDigitWidth + kDigitSpace;
 
         if (features.test(Features::RenderRate)) {
-            drawNumber(renderFps, left, color, *canvas);
+            if (idle) {
+                drawDash(left, *canvas);
+            } else {
+                drawNumber(renderFps, left, color, *canvas);
+            }
         }
         left += 3 * (kDigitWidth + kDigitSpace);
 
@@ -138,6 +147,14 @@
     SegmentDrawer::drawDigit(number % 10, left, color, canvas);
 }
 
+void RefreshRateOverlay::drawDash(int left, SkCanvas& canvas) {
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Middle, left, SK_ColorRED, canvas);
+
+    left += kDigitWidth + kDigitSpace;
+    SegmentDrawer::drawSegment(SegmentDrawer::Segment::Middle, left, SK_ColorRED, canvas);
+}
+
 std::unique_ptr<RefreshRateOverlay> RefreshRateOverlay::create(FpsRange range,
                                                                ftl::Flags<Features> features) {
     std::unique_ptr<RefreshRateOverlay> overlay =
@@ -171,7 +188,8 @@
     return mSurfaceControl != nullptr;
 }
 
-auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, Fps renderFps) -> const Buffers& {
+auto RefreshRateOverlay::getOrCreateBuffers(Fps refreshRate, Fps renderFps, bool idle)
+        -> const Buffers& {
     static const Buffers kNoBuffers;
     if (!mSurfaceControl) return kNoBuffers;
 
@@ -197,22 +215,16 @@
 
     createTransaction().setTransform(mSurfaceControl->get(), transform).apply();
 
-    BufferCache::const_iterator it =
-            mBufferCache.find({vsyncRate.getIntValue(), renderFps.getIntValue(), transformHint});
+    BufferCache::const_iterator it = mBufferCache.find(
+            {refreshRate.getIntValue(), renderFps.getIntValue(), transformHint, idle});
     if (it == mBufferCache.end()) {
-        // HWC minFps is not known by the framework in order
-        // to consider lower rates we set minFps to 0.
-        const int minFps = isSetByHwc() ? 0 : mFpsRange.min.getIntValue();
         const int maxFps = mFpsRange.max.getIntValue();
 
-        // Clamp to the range. The current vsyncRate may be outside of this range if the display
-        // has changed its set of supported refresh rates.
-        const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps);
+        // Clamp to supported refresh rate range: the current refresh rate may be outside of this
+        // range if the display has changed its set of supported refresh rates.
+        const int refreshIntFps = std::clamp(refreshRate.getIntValue(), 0, maxFps);
         const int renderIntFps = renderFps.getIntValue();
-
-        // Ensure non-zero range to avoid division by zero.
-        const float fpsScale =
-                static_cast<float>(displayIntFps - minFps) / std::max(1, maxFps - minFps);
+        const float fpsScale = static_cast<float>(refreshIntFps) / maxFps;
 
         constexpr SkColor kMinFpsColor = SK_ColorRED;
         constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
@@ -228,10 +240,10 @@
 
         const SkColor color = colorBase.toSkColor();
 
-        auto buffers = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
+        auto buffers = draw(refreshIntFps, renderIntFps, idle, color, transformHint, mFeatures);
         it = mBufferCache
-                     .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
-                     .first;
+                     .try_emplace({refreshIntFps, renderIntFps, transformHint, idle},
+                     std::move(buffers)).first;
     }
 
     return it->second;
@@ -260,25 +272,34 @@
     createTransaction().setLayerStack(mSurfaceControl->get(), stack).apply();
 }
 
-void RefreshRateOverlay::changeRefreshRate(Fps vsyncRate, Fps renderFps) {
-    mVsyncRate = vsyncRate;
+void RefreshRateOverlay::changeRefreshRate(Fps refreshRate, Fps renderFps) {
+    mRefreshRate = refreshRate;
     mRenderFps = renderFps;
-    const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame];
+    const auto buffer = getOrCreateBuffers(refreshRate, renderFps, mIsVrrIdle)[mFrame];
+    createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+}
+
+void RefreshRateOverlay::onVrrIdle(bool idle) {
+    mIsVrrIdle = idle;
+    if (!mRefreshRate || !mRenderFps) return;
+
+    const auto buffer = getOrCreateBuffers(*mRefreshRate, *mRenderFps, mIsVrrIdle)[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
 }
 
 void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
-    if (mFeatures.test(Features::RenderRate) && mVsyncRate && FlagManager::getInstance().misc1()) {
+    if (mFeatures.test(Features::RenderRate) && mRefreshRate &&
+        FlagManager::getInstance().misc1()) {
         mRenderFps = renderFps;
-        const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
+        const auto buffer = getOrCreateBuffers(*mRefreshRate, renderFps, mIsVrrIdle)[mFrame];
         createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
     }
 }
 
 void RefreshRateOverlay::animate() {
-    if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return;
+    if (!mFeatures.test(Features::Spinner) || !mRefreshRate) return;
 
-    const auto& buffers = getOrCreateBuffers(*mVsyncRate, *mRenderFps);
+    const auto& buffers = getOrCreateBuffers(*mRefreshRate, *mRenderFps, mIsVrrIdle);
     mFrame = (mFrame + 1) % buffers.size();
     const auto buffer = buffers[mFrame];
     createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index 0fec470..d8aa048 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -57,6 +57,7 @@
     void changeRenderRate(Fps);
     void animate();
     bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); }
+    void onVrrIdle(bool idle);
 
     RefreshRateOverlay(ConstructorTag, FpsRange, ftl::Flags<Features>);
 
@@ -65,30 +66,33 @@
 
     using Buffers = std::vector<sp<GraphicBuffer>>;
 
-    static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags,
-                        ftl::Flags<Features>);
+    static Buffers draw(int refreshRate, int renderFps, bool idle, SkColor,
+                        ui::Transform::RotationFlags, ftl::Flags<Features>);
     static void drawNumber(int number, int left, SkColor, SkCanvas&);
+    static void drawDash(int left, SkCanvas&);
 
-    const Buffers& getOrCreateBuffers(Fps, Fps);
+    const Buffers& getOrCreateBuffers(Fps, Fps, bool);
 
     SurfaceComposerClient::Transaction createTransaction() const;
 
     struct Key {
-        int vsyncRate;
+        int refreshRate;
         int renderFps;
         ui::Transform::RotationFlags flags;
+        bool idle;
 
         bool operator==(Key other) const {
-            return vsyncRate == other.vsyncRate && renderFps == other.renderFps &&
-                    flags == other.flags;
+            return refreshRate == other.refreshRate && renderFps == other.renderFps &&
+                    flags == other.flags && idle == other.idle;
         }
     };
 
     using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
     BufferCache mBufferCache;
 
-    std::optional<Fps> mVsyncRate;
+    std::optional<Fps> mRefreshRate;
     std::optional<Fps> mRenderFps;
+    bool mIsVrrIdle = false;
     size_t mFrame = 0;
 
     const FpsRange mFpsRange; // For color interpolation.
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index c888ccc..06c2f26 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -26,6 +26,7 @@
 
 #include "RegionSamplingThread.h"
 
+#include <common/trace.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/impl/OutputCompositionState.h>
 #include <cutils/properties.h>
@@ -34,7 +35,6 @@
 #include <gui/SyncScreenCaptureListener.h>
 #include <renderengine/impl/ExternalTexture.h>
 #include <ui/DisplayStatInfo.h>
-#include <utils/Trace.h>
 
 #include <string>
 
@@ -42,6 +42,7 @@
 #include "DisplayRenderArea.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "Layer.h"
+#include "RenderAreaBuilder.h"
 #include "Scheduler/VsyncController.h"
 #include "SurfaceFlinger.h"
 
@@ -147,7 +148,7 @@
     std::lock_guard lock(mThreadControlMutex);
 
     if (mSampleRequestTime.has_value()) {
-        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
+        SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForSamplePhase));
         mSampleRequestTime.reset();
         mFlinger.scheduleSample();
     }
@@ -165,7 +166,7 @@
     if (mLastSampleTime + mTunables.mSamplingPeriod > now) {
         // content changed, but we sampled not too long ago, so we need to sample some time in the
         // future.
-        ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::idleTimerWaiting));
+        SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::idleTimerWaiting));
         mSampleRequestTime = now;
         return;
     }
@@ -174,13 +175,13 @@
         // until the next vsync deadline, defer this sampling work
         // to a later frame, when hopefully there will be more time.
         if (samplingDeadline.has_value() && now + mTunables.mSamplingDuration > *samplingDeadline) {
-            ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
+            SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::waitForQuietFrame));
             mSampleRequestTime = mSampleRequestTime.value_or(now);
             return;
         }
     }
 
-    ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::sample));
+    SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::sample));
 
     mSampleRequestTime.reset();
     mLastSampleTime = now;
@@ -246,7 +247,7 @@
 }
 
 void RegionSamplingThread::captureSample() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mSamplingMutex);
 
     if (mDescriptors.empty()) {
@@ -276,12 +277,6 @@
     }
 
     const Rect sampledBounds = sampleRegion.bounds();
-    constexpr bool kHintForSeamlessTransition = false;
-
-    SurfaceFlinger::RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, sampledBounds, sampledBounds.getSize(),
-                                         ui::Dataspace::V0_SRGB, kHintForSeamlessTransition);
-    });
 
     std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> listeners;
 
@@ -319,39 +314,15 @@
         return true;
     };
 
-    std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshots;
-    if (mFlinger.mLayerLifecycleManagerEnabled) {
-        auto filterFn = [&](const frontend::LayerSnapshot& snapshot,
-                            bool& outStopTraversal) -> bool {
-            const Rect bounds =
-                    frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
-                                                          snapshot.transparentRegionHint);
-            const ui::Transform transform = snapshot.geomLayerTransform;
-            return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform,
-                                 outStopTraversal);
-        };
-        getLayerSnapshots =
-                mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
-                                                         filterFn);
-    } else {
-        auto traverseLayers = [&](const LayerVector::Visitor& visitor) {
-            bool stopLayerFound = false;
-            auto filterVisitor = [&](Layer* layer) {
-                // We don't want to capture any layers beyond the stop layer
-                if (stopLayerFound) return;
-
-                if (!layerFilterFn(layer->getDebugName(), layer->getSequence(),
-                                   Rect(layer->getBounds()), layer->getTransform(),
-                                   stopLayerFound)) {
-                    return;
-                }
-                visitor(layer);
-            };
-            mFlinger.traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {},
-                                                filterVisitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
+    auto filterFn = [&](const frontend::LayerSnapshot& snapshot, bool& outStopTraversal) -> bool {
+        const Rect bounds = frontend::RequestedLayerState::reduce(Rect(snapshot.geomLayerBounds),
+                                                                  snapshot.transparentRegionHint);
+        const ui::Transform transform = snapshot.geomLayerTransform;
+        return layerFilterFn(snapshot.name.c_str(), snapshot.path.id, bounds, transform,
+                             outStopTraversal);
+    };
+    auto getLayerSnapshotsFn =
+            mFlinger.getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID, filterFn);
 
     std::shared_ptr<renderengine::ExternalTexture> buffer = nullptr;
     if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() &&
@@ -375,12 +346,30 @@
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
     constexpr bool kIsProtected = false;
+    constexpr bool kAttachGainmap = false;
 
-    if (const auto fenceResult =
-                mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
-                                             kRegionSampling, kGrayscale, kIsProtected, nullptr)
-                        .get();
-        fenceResult.ok()) {
+    SurfaceFlinger::RenderAreaBuilderVariant
+            renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
+                              sampledBounds.getSize(), ui::Dataspace::V0_SRGB, displayWeak,
+                              RenderArea::Options::CAPTURE_SECURE_LAYERS);
+
+    FenceResult fenceResult;
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise() && mFlinger.mRenderEngine->isThreaded()) {
+        std::vector<sp<LayerFE>> layerFEs;
+        auto displayState = mFlinger.getSnapshotsFromMainThread(renderAreaBuilder,
+                                                                getLayerSnapshotsFn, layerFEs);
+        fenceResult = mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling,
+                                                 kGrayscale, kIsProtected, kAttachGainmap, nullptr,
+                                                 displayState, layerFEs)
+                              .get();
+    } else {
+        fenceResult = mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn,
+                                                       buffer, kRegionSampling, kGrayscale,
+                                                       kIsProtected, kAttachGainmap, nullptr)
+                              .get();
+    }
+    if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
     }
 
@@ -405,7 +394,7 @@
     }
 
     mCachedBuffer = buffer;
-    ATRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
+    SFTRACE_INT(lumaSamplingStepTag, static_cast<int>(samplingStep::noWorkNeeded));
 }
 
 // NO_THREAD_SAFETY_ANALYSIS is because std::unique_lock presently lacks thread safety annotations.
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 5de148e..aa66ccf 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -4,6 +4,8 @@
 #include <ui/Transform.h>
 
 #include <functional>
+
+#include "FrontEnd/LayerSnapshot.h"
 #include "Layer.h"
 
 namespace android {
@@ -19,37 +21,26 @@
 class RenderArea {
 public:
     enum class CaptureFill {CLEAR, OPAQUE};
+    enum class Options {
+        // If not set, the secure layer would be blacked out or skipped
+        // when rendered to an insecure render area
+        CAPTURE_SECURE_LAYERS = 1 << 0,
 
+        // If set, the render result may be used for system animations
+        // that must preserve the exact colors of the display
+        HINT_FOR_SEAMLESS_TRANSITION = 1 << 1,
+    };
     static float getCaptureFillValue(CaptureFill captureFill);
 
     RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
-               bool hintForSeamlessTransition, bool allowSecureLayers = false)
-          : mAllowSecureLayers(allowSecureLayers),
+               ftl::Flags<Options> options)
+          : mOptions(options),
             mReqSize(reqSize),
             mReqDataSpace(reqDataSpace),
-            mCaptureFill(captureFill),
-            mHintForSeamlessTransition(hintForSeamlessTransition) {}
-
-    static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
-            std::function<void(const LayerVector::Visitor&)> traverseLayers) {
-        return [traverseLayers = std::move(traverseLayers)]() {
-            std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
-            traverseLayers([&](Layer* layer) {
-                // Layer::prepareClientComposition uses the layer's snapshot to populate the
-                // resulting LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings
-                // are generated with the layer's current buffer and geometry.
-                layer->updateSnapshot(true /* updateGeometry */);
-                layers.emplace_back(layer, layer->copyCompositionEngineLayerFE());
-            });
-            return layers;
-        };
-    }
+            mCaptureFill(captureFill) {}
 
     virtual ~RenderArea() = default;
 
-    // Invoke drawLayers to render layers into the render area.
-    virtual void render(std::function<void()> drawLayers) { drawLayers(); }
-
     // Returns true if the render area is secure.  A secure layer should be
     // blacked out / skipped when rendered to an insecure render area.
     virtual bool isSecure() const = 0;
@@ -85,18 +76,23 @@
     // capture operation.
     virtual sp<Layer> getParentLayer() const { return nullptr; }
 
+    // If this is a LayerRenderArea, return the layer snapshot
+    // of the root layer of the capture operation
+    virtual const frontend::LayerSnapshot* getLayerSnapshot() const { return nullptr; }
+
     // Returns whether the render result may be used for system animations that
     // must preserve the exact colors of the display.
-    bool getHintForSeamlessTransition() const { return mHintForSeamlessTransition; }
+    bool getHintForSeamlessTransition() const {
+        return mOptions.test(Options::HINT_FOR_SEAMLESS_TRANSITION);
+    }
 
 protected:
-    const bool mAllowSecureLayers;
+    ftl::Flags<Options> mOptions;
 
 private:
     const ui::Size mReqSize;
     const ui::Dataspace mReqDataSpace;
     const CaptureFill mCaptureFill;
-    const bool mHintForSeamlessTransition;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/RenderAreaBuilder.h b/services/surfaceflinger/RenderAreaBuilder.h
new file mode 100644
index 0000000..599fa7e
--- /dev/null
+++ b/services/surfaceflinger/RenderAreaBuilder.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2024 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 "DisplayDevice.h"
+#include "DisplayRenderArea.h"
+#include "LayerRenderArea.h"
+#include "ui/Size.h"
+#include "ui/Transform.h"
+
+namespace android {
+/**
+ * A parameter object for creating a render area
+ */
+struct RenderAreaBuilder {
+    // Source crop of the render area
+    Rect crop;
+
+    // Size of the physical render area
+    ui::Size reqSize;
+
+    // Composition data space of the render area
+    ui::Dataspace reqDataSpace;
+
+    ftl::Flags<RenderArea::Options> options;
+    virtual std::unique_ptr<RenderArea> build() const = 0;
+
+    RenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                      ftl::Flags<RenderArea::Options> options)
+          : crop(crop), reqSize(reqSize), reqDataSpace(reqDataSpace), options(options) {}
+
+    virtual ~RenderAreaBuilder() = default;
+};
+
+struct DisplayRenderAreaBuilder : RenderAreaBuilder {
+    DisplayRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace,
+                             wp<const DisplayDevice> displayWeak,
+                             ftl::Flags<RenderArea::Options> options)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, options), displayWeak(displayWeak) {}
+
+    // Display that render area will be on
+    wp<const DisplayDevice> displayWeak;
+
+    std::unique_ptr<RenderArea> build() const override {
+        return DisplayRenderArea::create(displayWeak, crop, reqSize, reqDataSpace, options);
+    }
+};
+
+struct LayerRenderAreaBuilder : RenderAreaBuilder {
+    LayerRenderAreaBuilder(Rect crop, ui::Size reqSize, ui::Dataspace reqDataSpace, sp<Layer> layer,
+                           bool childrenOnly, ftl::Flags<RenderArea::Options> options)
+          : RenderAreaBuilder(crop, reqSize, reqDataSpace, options),
+            layer(layer),
+            childrenOnly(childrenOnly) {}
+
+    // Root layer of the render area
+    sp<Layer> layer;
+
+    // Layer snapshot of the root layer
+    frontend::LayerSnapshot layerSnapshot;
+
+    // Transform to be applied on the layers to transform them
+    // into the logical render area
+    ui::Transform layerTransform{ui::Transform()};
+
+    // Buffer bounds
+    Rect layerBufferSize{Rect()};
+
+    // If false, transform is inverted from the parent snapshot
+    bool childrenOnly;
+
+    // Uses parent snapshot to determine layer transform and buffer size
+    void setLayerSnapshot(const frontend::LayerSnapshot& parentSnapshot) {
+        layerSnapshot = parentSnapshot;
+        if (!childrenOnly) {
+            layerTransform = parentSnapshot.localTransform.inverse();
+        }
+        layerBufferSize = parentSnapshot.bufferSize;
+    }
+
+    std::unique_ptr<RenderArea> build() const override {
+        return std::make_unique<LayerRenderArea>(layer, std::move(layerSnapshot), crop, reqSize,
+                                                 reqDataSpace, layerTransform, layerBufferSize,
+                                                 options);
+    }
+};
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/Android.bp b/services/surfaceflinger/Scheduler/Android.bp
index 16776cf..5455fdc 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -53,7 +53,10 @@
 cc_test {
     name: "libscheduler_test",
     test_suites: ["device-tests"],
-    defaults: ["libscheduler_defaults"],
+    defaults: [
+        "libscheduler_defaults",
+        "libsurfaceflinger_common_test_deps",
+    ],
     srcs: [
         "tests/FrameTargeterTest.cpp",
         "tests/PresentLatencyTrackerTest.cpp",
@@ -63,9 +66,5 @@
         "libgmock",
         "libgtest",
         "libscheduler",
-        "libsurfaceflingerflags_test",
-    ],
-    shared_libs: [
-        "server_configurable_flags",
     ],
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 96eccf2..218c56e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -33,7 +33,7 @@
 #include <android-base/stringprintf.h>
 
 #include <binder/IPCThreadState.h>
-
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/sched_policy.h>
 
@@ -41,7 +41,6 @@
 #include <gui/SchedulingPolicy.h>
 
 #include <utils/Errors.h>
-#include <utils/Trace.h>
 
 #include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
@@ -226,16 +225,17 @@
 }
 
 binder::Status EventThreadConnection::requestNextVsync() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mEventThread->requestNextVsync(sp<EventThreadConnection>::fromExisting(this));
     return binder::Status::ok();
 }
 
 binder::Status EventThreadConnection::getLatestVsyncEventData(
         ParcelableVsyncEventData* outVsyncEventData) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     outVsyncEventData->vsync =
-            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this));
+            mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this),
+                                                  systemTime());
     return binder::Status::ok();
 }
 
@@ -320,7 +320,8 @@
 
     mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
                                .readyDuration = mReadyDuration.count(),
-                               .lastVsync = mLastVsyncCallbackTime.ns()});
+                               .lastVsync = mLastVsyncCallbackTime.ns(),
+                               .committedVsyncOpt = mLastCommittedVsyncTime.ns()});
 }
 
 sp<EventThreadConnection> EventThread::createEventConnection(
@@ -387,8 +388,8 @@
     }
 }
 
-VsyncEventData EventThread::getLatestVsyncEventData(
-        const sp<EventThreadConnection>& connection) const {
+VsyncEventData EventThread::getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                    nsecs_t now) const {
     // Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
     // way to get vsync data (instead of posting callbacks to Choreographer).
     mCallback.resync();
@@ -399,11 +400,10 @@
     const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
         std::lock_guard<std::mutex> lock(mMutex);
         const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
-                systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+                now + mWorkDuration.get().count() + mReadyDuration.count());
         return {vsyncTime, vsyncTime - mReadyDuration.count()};
     }();
-    generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
-                          presentTime, deadline);
+    generateFrameTimeline(vsyncEventData, frameInterval.ns(), now, presentTime, deadline);
     if (FlagManager::getInstance().vrr_config()) {
         mCallback.onExpectedPresentTimePosted(TimePoint::fromNs(presentTime));
     }
@@ -528,10 +528,11 @@
         }
 
         if (mState == State::VSync) {
-            const auto scheduleResult =
-                    mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
-                                                 .readyDuration = mReadyDuration.count(),
-                                                 .lastVsync = mLastVsyncCallbackTime.ns()});
+            const auto scheduleResult = mVsyncRegistration.schedule(
+                    {.workDuration = mWorkDuration.get().count(),
+                     .readyDuration = mReadyDuration.count(),
+                     .lastVsync = mLastVsyncCallbackTime.ns(),
+                     .committedVsyncOpt = mLastCommittedVsyncTime.ns()});
             LOG_ALWAYS_FATAL_IF(!scheduleResult, "Error scheduling callback");
         } else {
             mVsyncRegistration.cancel();
@@ -726,8 +727,9 @@
     }
     if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC &&
         FlagManager::getInstance().vrr_config()) {
-        mCallback.onExpectedPresentTimePosted(
-                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime()));
+        mLastCommittedVsyncTime =
+                TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
+        mCallback.onExpectedPresentTimePosted(mLastCommittedVsyncTime);
     }
 }
 
@@ -745,9 +747,12 @@
 
     const auto relativeLastCallTime =
             ticks<std::milli, float>(mLastVsyncCallbackTime - TimePoint::now());
+    const auto relativeLastCommittedTime =
+            ticks<std::milli, float>(mLastCommittedVsyncTime - TimePoint::now());
     StringAppendF(&result, "mWorkDuration=%.2f mReadyDuration=%.2f last vsync time ",
                   mWorkDuration.get().count() / 1e6f, mReadyDuration.count() / 1e6f);
     StringAppendF(&result, "%.2fms relative to now\n", relativeLastCallTime);
+    StringAppendF(&result, " with vsync committed at %.2fms", relativeLastCommittedTime);
 
     StringAppendF(&result, "  pending events (count=%zu):\n", mPendingEvents.size());
     for (const auto& event : mPendingEvents) {
@@ -795,7 +800,8 @@
     if (reschedule) {
         mVsyncRegistration.schedule({.workDuration = mWorkDuration.get().count(),
                                      .readyDuration = mReadyDuration.count(),
-                                     .lastVsync = mLastVsyncCallbackTime.ns()});
+                                     .lastVsync = mLastVsyncCallbackTime.ns(),
+                                     .committedVsyncOpt = mLastCommittedVsyncTime.ns()});
     }
     return oldRegistration;
 }
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index 90e61a9..bbe4f9d 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -127,8 +127,8 @@
     virtual void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) = 0;
     // Requests the next vsync. If resetIdleTimer is set to true, it resets the idle timer.
     virtual void requestNextVsync(const sp<EventThreadConnection>& connection) = 0;
-    virtual VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const = 0;
+    virtual VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                                   nsecs_t now) const = 0;
 
     virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
 
@@ -160,8 +160,8 @@
     status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
     void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
     void requestNextVsync(const sp<EventThreadConnection>& connection) override;
-    VsyncEventData getLatestVsyncEventData(
-            const sp<EventThreadConnection>& connection) const override;
+    VsyncEventData getLatestVsyncEventData(const sp<EventThreadConnection>& connection,
+                                           nsecs_t now) const override;
 
     void enableSyntheticVsync(bool) override;
 
@@ -220,6 +220,7 @@
     std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex);
     std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule GUARDED_BY(mMutex);
     TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now();
+    TimePoint mLastCommittedVsyncTime GUARDED_BY(mMutex) = TimePoint::now();
     scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
     frametimeline::TokenManager* const mTokenManager;
 
diff --git a/services/surfaceflinger/Scheduler/ISchedulerCallback.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 9f4f5b6..d02d149 100644
--- a/services/surfaceflinger/Scheduler/ISchedulerCallback.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -28,10 +28,11 @@
     virtual void requestHardwareVsync(PhysicalDisplayId, bool enabled) = 0;
     virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
     virtual void kernelTimerChanged(bool expired) = 0;
-    virtual void triggerOnFrameRateOverridesChanged() = 0;
     virtual void onChoreographerAttached() = 0;
     virtual void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>,
                                              Fps renderRate) = 0;
+    virtual void onCommitNotComposited() = 0;
+    virtual void vrrDisplayIdle(bool idle) = 0;
 
 protected:
     ~ISchedulerCallback() = default;
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index b8d5e76..64b85c0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -21,8 +21,8 @@
 #include "LayerHistory.h"
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <cutils/properties.h>
-#include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
 
@@ -40,14 +40,15 @@
 
 namespace {
 
-bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
+bool isLayerActive(const LayerInfo& info, nsecs_t threshold, bool isVrrDevice) {
     if (FlagManager::getInstance().misc1() && !info.isVisible()) {
         return false;
     }
 
     // Layers with an explicit frame rate or frame rate category are kept active,
     // but ignore NoVote.
-    if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
+    const auto frameRate = info.getSetFrameRateVote();
+    if (frameRate.isValid() && !frameRate.isNoVote() && frameRate.isVoteValidForMrr(isVrrDevice)) {
         return true;
     }
 
@@ -71,7 +72,7 @@
 
 void trace(const LayerInfo& info, LayerHistory::LayerVoteType type, int fps) {
     const auto traceType = [&](LayerHistory::LayerVoteType checkedType, int value) {
-        ATRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
+        SFTRACE_INT(info.getTraceTag(checkedType), type == checkedType ? value : 0);
     };
 
     traceType(LayerHistory::LayerVoteType::NoVote, 1);
@@ -108,12 +109,12 @@
 
 LayerHistory::~LayerHistory() = default;
 
-void LayerHistory::registerLayer(Layer* layer, bool contentDetectionEnabled) {
+void LayerHistory::registerLayer(Layer* layer, bool contentDetectionEnabled,
+                                 FrameRateCompatibility frameRateCompatibility) {
     std::lock_guard lock(mLock);
     LOG_ALWAYS_FATAL_IF(findLayer(layer->getSequence()).first != LayerStatus::NotFound,
                         "%s already registered", layer->getName().c_str());
-    LayerVoteType type =
-            getVoteType(layer->getDefaultFrameRateCompatibility(), contentDetectionEnabled);
+    LayerVoteType type = getVoteType(frameRateCompatibility, contentDetectionEnabled);
     auto info = std::make_unique<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
 
     // The layer can be placed on either map, it is assumed that partitionLayers() will be called
@@ -189,12 +190,12 @@
 }
 
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     Summary summary;
 
     std::lock_guard lock(mLock);
 
-    partitionLayers(now);
+    partitionLayers(now, selector.isVrrDevice());
 
     for (const auto& [key, value] : mActiveLayerInfos) {
         auto& info = value.second;
@@ -203,7 +204,7 @@
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
               layerFocused ? "" : "not");
 
-        ATRACE_FORMAT("%s", info->getName().c_str());
+        SFTRACE_FORMAT("%s", info->getName().c_str());
         const auto votes = info->getRefreshRateVote(selector, now);
         for (LayerInfo::LayerVote vote : votes) {
             if (vote.isNoVote()) {
@@ -221,8 +222,8 @@
             const std::string categoryString = vote.category == FrameRateCategory::Default
                     ? ""
                     : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str());
-            ATRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(),
-                                  to_string(vote.fps).c_str(), categoryString.c_str(), weight);
+            SFTRACE_FORMAT_INSTANT("%s %s %s (%.2f)", ftl::enum_string(vote.type).c_str(),
+                                   to_string(vote.fps).c_str(), categoryString.c_str(), weight);
             summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
                                vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
                                weight, layerFocused});
@@ -236,15 +237,15 @@
     return summary;
 }
 
-void LayerHistory::partitionLayers(nsecs_t now) {
-    ATRACE_CALL();
+void LayerHistory::partitionLayers(nsecs_t now, bool isVrrDevice) {
+    SFTRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
     // iterate over inactive map
     LayerInfos::iterator it = mInactiveLayerInfos.begin();
     while (it != mInactiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // move this to the active map
 
             mActiveLayerInfos.insert({it->first, std::move(it->second)});
@@ -262,7 +263,7 @@
     it = mActiveLayerInfos.begin();
     while (it != mActiveLayerInfos.end()) {
         auto& [layerUnsafe, info] = it->second;
-        if (isLayerActive(*info, threshold)) {
+        if (isLayerActive(*info, threshold, isVrrDevice)) {
             // Set layer vote if set
             const auto frameRate = info->getSetFrameRateVote();
 
@@ -279,9 +280,18 @@
                     case Layer::FrameRateCompatibility::Exact:
                         return LayerVoteType::ExplicitExact;
                     case Layer::FrameRateCompatibility::Gte:
-                        return LayerVoteType::ExplicitGte;
+                        if (isVrrDevice) {
+                            return LayerVoteType::ExplicitGte;
+                        } else {
+                            // For MRR, treat GTE votes as Max because it is used for animations and
+                            // scroll. MRR cannot change frame rate without jank, so it should
+                            // prefer smoothness.
+                            return LayerVoteType::Max;
+                        }
                 }
             }();
+            const bool isValuelessVote = voteType == LayerVoteType::NoVote ||
+                    voteType == LayerVoteType::Min || voteType == LayerVoteType::Max;
 
             if (FlagManager::getInstance().game_default_frame_rate()) {
                 // Determine the layer frame rate considering the following priorities:
@@ -300,13 +310,14 @@
 
                 if (gameModeFrameRateOverride.isValid()) {
                     info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
-                    ATRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
+                    SFTRACE_FORMAT_INSTANT("GameModeFrameRateOverride");
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameModeFrameRateOverride.getIntValue());
                     }
-                } else if (frameRate.isValid()) {
-                    info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
+                } else if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
+                    info->setLayerVote({setFrameRateVoteType,
+                                        isValuelessVote ? 0_Hz : frameRate.vote.rate,
                                         frameRate.vote.seamlessness, frameRate.category});
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
@@ -315,20 +326,36 @@
                 } else if (gameDefaultFrameRateOverride.isValid()) {
                     info->setLayerVote(
                             {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
-                    ATRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
+                    SFTRACE_FORMAT_INSTANT("GameDefaultFrameRateOverride");
                     if (CC_UNLIKELY(mTraceEnabled)) {
                         trace(*info, gameFrameRateOverrideVoteType,
                               gameDefaultFrameRateOverride.getIntValue());
                     }
                 } else {
+                    if (frameRate.isValid() && !frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        SFTRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                               "%s %s",
+                                               info->getName().c_str(),
+                                               ftl::enum_string(frameRate.vote.type).c_str(),
+                                               to_string(frameRate.vote.rate).c_str(),
+                                               ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             } else {
-                if (frameRate.isValid()) {
+                if (frameRate.isValid() && frameRate.isVoteValidForMrr(isVrrDevice)) {
                     const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
-                    info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
-                                        frameRate.category});
+                    info->setLayerVote({type, isValuelessVote ? 0_Hz : frameRate.vote.rate,
+                                        frameRate.vote.seamlessness, frameRate.category});
                 } else {
+                    if (!frameRate.isVoteValidForMrr(isVrrDevice)) {
+                        SFTRACE_FORMAT_INSTANT("Reset layer to ignore explicit vote on MRR %s: %s "
+                                               "%s %s",
+                                               info->getName().c_str(),
+                                               ftl::enum_string(frameRate.vote.type).c_str(),
+                                               to_string(frameRate.vote.rate).c_str(),
+                                               ftl::enum_string(frameRate.category).c_str());
+                    }
                     info->resetLayerVote();
                 }
             }
@@ -394,7 +421,7 @@
 bool LayerHistory::isSmallDirtyArea(uint32_t dirtyArea, float threshold) const {
     const float ratio = (float)dirtyArea / mDisplayArea;
     const bool isSmallDirty = ratio <= threshold;
-    ATRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
+    SFTRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
     return isSmallDirty;
 }
 
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index a6f1b56..e3babba 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -51,7 +51,8 @@
     ~LayerHistory();
 
     // Layers are unregistered when the weak reference expires.
-    void registerLayer(Layer*, bool contentDetectionEnabled);
+    void registerLayer(Layer*, bool contentDetectionEnabled,
+                       FrameRateCompatibility frameRateCompatibility);
 
     // Sets the display size. Client is responsible for synchronization.
     void setDisplayArea(uint32_t displayArea) { mDisplayArea = displayArea; }
@@ -111,9 +112,12 @@
     std::string dumpGameFrameRateOverridesLocked() const REQUIRES(mLock);
 
     // Iterates over layers maps moving all active layers to mActiveLayerInfos and all inactive
-    // layers to mInactiveLayerInfos.
+    // layers to mInactiveLayerInfos. Layer's active state is determined by multiple factors
+    // such as update activity, visibility, and frame rate vote.
     // worst case time complexity is O(2 * inactive + active)
-    void partitionLayers(nsecs_t now) REQUIRES(mLock);
+    // now: the current time (system time) when calling the method
+    // isVrrDevice: true if the device has DisplayMode with VrrConfig specified.
+    void partitionLayers(nsecs_t now, bool isVrrDevice) REQUIRES(mLock);
 
     enum class LayerStatus {
         NotFound,
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 9745452..ff1926e 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -27,10 +27,10 @@
 #include <utility>
 
 #include <android/native_window.h>
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/trace.h>
 #include <ftl/enum.h>
-#include <gui/TraceUtils.h>
 #include <system/window.h>
 
 #undef LOG_TAG
@@ -55,10 +55,10 @@
                                    bool pendingModeChange, const LayerProps& props) {
     lastPresentTime = std::max(lastPresentTime, static_cast<nsecs_t>(0));
 
-    mLastUpdatedTime = std::max(lastPresentTime, now);
     *mLayerProps = props;
     switch (updateType) {
         case LayerUpdateType::AnimationTX:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             mLastAnimationTime = std::max(lastPresentTime, now);
             break;
         case LayerUpdateType::SetFrameRate:
@@ -67,6 +67,7 @@
             }
             FALLTHROUGH_INTENDED;
         case LayerUpdateType::Buffer:
+            mLastUpdatedTime = std::max(lastPresentTime, now);
             FrameTimeData frameTime = {.presentTime = lastPresentTime,
                                        .queueTime = mLastUpdatedTime,
                                        .pendingModeChange = pendingModeChange,
@@ -180,19 +181,19 @@
 bool LayerInfo::hasEnoughDataForHeuristic() const {
     // The layer had to publish at least HISTORY_SIZE or HISTORY_DURATION of updates
     if (mFrameTimes.size() < 2) {
-        ALOGV("fewer than 2 frames recorded: %zu", mFrameTimes.size());
+        ALOGV("%s fewer than 2 frames recorded: %zu", mName.c_str(), mFrameTimes.size());
         return false;
     }
 
     if (!isFrameTimeValid(mFrameTimes.front())) {
-        ALOGV("stale frames still captured");
+        ALOGV("%s stale frames still captured", mName.c_str());
         return false;
     }
 
     const auto totalDuration = mFrameTimes.back().queueTime - mFrameTimes.front().queueTime;
     if (mFrameTimes.size() < HISTORY_SIZE && totalDuration < HISTORY_DURATION.count()) {
-        ALOGV("not enough frames captured: %zu | %.2f seconds", mFrameTimes.size(),
-              totalDuration / 1e9f);
+        ALOGV("%s not enough frames captured: %zu | %.2f seconds", mName.c_str(),
+              mFrameTimes.size(), totalDuration / 1e9f);
         return false;
     }
 
@@ -258,7 +259,7 @@
     }
 
     if (smallDirtyCount > 0) {
-        ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+        SFTRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
     }
 
     if (numDeltas == 0) {
@@ -271,7 +272,7 @@
 
 std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
                                                              nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -306,7 +307,7 @@
 
 LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
                                                           nsecs_t now) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     LayerInfo::RefreshRateVotes votes;
 
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
@@ -314,8 +315,8 @@
             const auto voteType = mLayerVote.type == LayerHistory::LayerVoteType::NoVote
                     ? LayerHistory::LayerVoteType::NoVote
                     : LayerHistory::LayerVoteType::ExplicitCategory;
-            ATRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
-                                  ftl::enum_string(mLayerVote.category).c_str());
+            SFTRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
+                                   ftl::enum_string(mLayerVote.category).c_str());
             ALOGV("%s voted %s with category: %s", mName.c_str(),
                   ftl::enum_string(voteType).c_str(),
                   ftl::enum_string(mLayerVote.category).c_str());
@@ -325,7 +326,7 @@
 
         if (mLayerVote.fps.isValid() ||
             mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
-            ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
+            SFTRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
             ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type));
             votes.push_back({mLayerVote.type, mLayerVote.fps, mLayerVote.seamlessness,
                              FrameRateCategory::Default, mLayerVote.categorySmoothSwitchOnly});
@@ -335,7 +336,7 @@
     }
 
     if (isAnimating(now)) {
-        ATRACE_FORMAT_INSTANT("animating");
+        SFTRACE_FORMAT_INSTANT("animating");
         ALOGV("%s is animating", mName.c_str());
         mLastRefreshRate.animating = true;
         votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
@@ -344,7 +345,7 @@
 
     // Vote for max refresh rate whenever we're front-buffered.
     if (FlagManager::getInstance().vrr_config() && isFrontBuffered()) {
-        ATRACE_FORMAT_INSTANT("front buffered");
+        SFTRACE_FORMAT_INSTANT("front buffered");
         ALOGV("%s is front-buffered", mName.c_str());
         votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
         return votes;
@@ -353,7 +354,7 @@
     const LayerInfo::Frequent frequent = isFrequent(now);
     mIsFrequencyConclusive = frequent.isConclusive;
     if (!frequent.isFrequent) {
-        ATRACE_FORMAT_INSTANT("infrequent");
+        SFTRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
         mLastRefreshRate.infrequent = true;
         mLastSmallDirtyCount = 0;
@@ -364,12 +365,14 @@
     }
 
     if (frequent.clearHistory) {
+        SFTRACE_FORMAT_INSTANT("frequent.clearHistory");
+        ALOGV("%s frequent.clearHistory", mName.c_str());
         clearHistory(now);
     }
 
     // Return no vote if the recent frames are small dirty.
     if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
-        ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
+        SFTRACE_FORMAT_INSTANT("NoVote (small dirty)");
         ALOGV("%s is small dirty", mName.c_str());
         votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
         return votes;
@@ -377,13 +380,13 @@
 
     auto refreshRate = calculateRefreshRateIfPossible(selector, now);
     if (refreshRate.has_value()) {
-        ATRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
+        SFTRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
         ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
         votes.push_back({LayerHistory::LayerVoteType::Heuristic, refreshRate.value()});
         return votes;
     }
 
-    ATRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
+    SFTRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
     ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
     votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
     return votes;
@@ -449,7 +452,7 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
     }
 
     return selectRefreshRate(selector);
@@ -483,9 +486,9 @@
             mHeuristicTraceTagData = makeHeuristicTraceTagData();
         }
 
-        ATRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
-        ATRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
-        ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
+        SFTRACE_INT(mHeuristicTraceTagData->max.c_str(), max->refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->min.c_str(), min->refreshRate.getIntValue());
+        SFTRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
     }
 
     return consistent ? maxClosestRate : Fps();
@@ -562,8 +565,42 @@
     return vote.type == FrameRateCompatibility::NoVote;
 }
 
+bool LayerInfo::FrameRate::isValuelessType() const {
+    // For a valueless frame rate compatibility (type), the frame rate should be unspecified (0 Hz).
+    if (!isApproxEqual(vote.rate, 0_Hz)) {
+        return false;
+    }
+    switch (vote.type) {
+        case FrameRateCompatibility::Min:
+        case FrameRateCompatibility::NoVote:
+            return true;
+        case FrameRateCompatibility::Default:
+        case FrameRateCompatibility::ExactOrMultiple:
+        case FrameRateCompatibility::Exact:
+        case FrameRateCompatibility::Gte:
+            return false;
+    }
+}
+
 bool LayerInfo::FrameRate::isValid() const {
-    return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
+    return isValuelessType() || vote.rate.isValid() || category != FrameRateCategory::Default;
+}
+
+bool LayerInfo::FrameRate::isVoteValidForMrr(bool isVrrDevice) const {
+    if (isVrrDevice || FlagManager::getInstance().frame_rate_category_mrr()) {
+        return true;
+    }
+
+    if (category == FrameRateCategory::Default) {
+        return true;
+    }
+
+    if (category == FrameRateCategory::NoPreference && vote.rate.isValid() &&
+        vote.type == FrameRateCompatibility::ExactOrMultiple) {
+        return true;
+    }
+
+    return false;
 }
 
 std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 326e444..a7847bc 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -146,6 +146,13 @@
         // selection.
         bool isNoVote() const;
 
+        // Returns true if the FrameRate has a valid valueless (0 Hz) frame rate type.
+        bool isValuelessType() const;
+
+        // Checks whether the given FrameRate's vote specifications is valid for MRR devices
+        // given the current flagging.
+        bool isVoteValidForMrr(bool isVrrDevice) const;
+
     private:
         static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
             if (!rate.isValid()) {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index ff88d71..2e1f938 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -57,7 +57,7 @@
         mHandler(std::move(handler)) {}
 
 void MessageQueue::vsyncCallback(nsecs_t vsyncTime, nsecs_t targetWakeupTime, nsecs_t readyTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     // Trace VSYNC-sf
     mVsync.value = (mVsync.value + 1) % 2;
 
@@ -136,7 +136,7 @@
 }
 
 void MessageQueue::setDuration(std::chrono::nanoseconds workDuration) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mVsync.mutex);
     mVsync.workDuration = workDuration;
     mVsync.scheduledFrameTimeOpt =
@@ -188,12 +188,13 @@
     postMessage(sp<ConfigureHandler>::make(mCompositor));
 }
 
-void MessageQueue::scheduleFrame() {
-    ATRACE_CALL();
+void MessageQueue::scheduleFrame(Duration workDurationSlack) {
+    SFTRACE_CALL();
 
     std::lock_guard lock(mVsync.mutex);
+    const auto workDuration = Duration(mVsync.workDuration.get() - workDurationSlack);
     mVsync.scheduledFrameTimeOpt =
-            mVsync.registration->schedule({.workDuration = mVsync.workDuration.get().count(),
+            mVsync.registration->schedule({.workDuration = workDuration.ns(),
                                            .readyDuration = 0,
                                            .lastVsync = mVsync.lastCallbackTime.ns()});
 }
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index c5fc371..ba1efbe 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -74,7 +74,7 @@
     virtual void postMessage(sp<MessageHandler>&&) = 0;
     virtual void postMessageDelayed(sp<MessageHandler>&&, nsecs_t uptimeDelay) = 0;
     virtual void scheduleConfigure() = 0;
-    virtual void scheduleFrame() = 0;
+    virtual void scheduleFrame(Duration workDurationSlack = Duration::fromNs(0)) = 0;
 
     virtual std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const = 0;
 };
@@ -149,7 +149,7 @@
     void postMessageDelayed(sp<MessageHandler>&&, nsecs_t uptimeDelay) override;
 
     void scheduleConfigure() override;
-    void scheduleFrame() override;
+    void scheduleFrame(Duration workDurationSlack = Duration::fromNs(0)) override;
 
     std::optional<scheduler::ScheduleResult> getScheduledFrameResult() const override;
 };
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index cd45bfd..7e61dc0 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -115,9 +115,24 @@
             break;
         }
 
-        auto triggerTime = mClock->now() + mInterval;
+        auto triggerTime = mClock->now() + mInterval.load();
         state = TimerState::WAITING;
         while (true) {
+            if (mPaused) {
+                mWaiting = true;
+                int result = sem_wait(&mSemaphore);
+                if (result && errno != EINTR) {
+                    std::stringstream ss;
+                    ss << "sem_wait failed (" << errno << ")";
+                    LOG_ALWAYS_FATAL("%s", ss.str().c_str());
+                }
+
+                mWaiting = false;
+                state = checkForResetAndStop(state);
+                if (state == TimerState::STOPPED) {
+                    break;
+                }
+            }
             // Wait until triggerTime time to check if we need to reset or drop into the idle state.
             if (const auto triggerInterval = triggerTime - mClock->now(); triggerInterval > 0ns) {
                 mWaiting = true;
@@ -137,14 +152,14 @@
                 break;
             }
 
-            if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
+            if (!mPaused && state == TimerState::WAITING && (triggerTime - mClock->now()) <= 0ns) {
                 triggerTimeout = true;
                 state = TimerState::IDLE;
                 break;
             }
 
             if (state == TimerState::RESET) {
-                triggerTime = mLastResetTime.load() + mInterval;
+                triggerTime = mLastResetTime.load() + mInterval.load();
                 state = TimerState::WAITING;
             }
         }
@@ -179,5 +194,15 @@
     }
 }
 
+void OneShotTimer::pause() {
+    mPaused = true;
+}
+
+void OneShotTimer::resume() {
+    if (mPaused.exchange(false)) {
+        LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed");
+    }
+}
+
 } // namespace scheduler
 } // namespace android
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.h b/services/surfaceflinger/Scheduler/OneShotTimer.h
index 02e8719..4e1e2ee 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -43,7 +43,8 @@
                  std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>());
     ~OneShotTimer();
 
-    Duration interval() const { return mInterval; }
+    Duration interval() const { return mInterval.load(); }
+    void setInterval(Interval value) { mInterval = value; }
 
     // Initializes and turns on the idle timer.
     void start();
@@ -51,6 +52,10 @@
     void stop();
     // Resets the wakeup time and fires the reset callback.
     void reset();
+    // Pauses the timer. reset calls will get ignored.
+    void pause();
+    // Resumes the timer.
+    void resume();
 
 private:
     // Enum to track in what state is the timer.
@@ -91,7 +96,7 @@
     std::string mName;
 
     // Interval after which timer expires.
-    const Interval mInterval;
+    std::atomic<Interval> mInterval;
 
     // Callback that happens when timer resets.
     const ResetCallback mResetCallback;
@@ -105,6 +110,7 @@
     std::atomic<bool> mResetTriggered = false;
     std::atomic<bool> mStopTriggered = false;
     std::atomic<bool> mWaiting = false;
+    std::atomic<bool> mPaused = false;
     std::atomic<std::chrono::steady_clock::time_point> mLastResetTime;
 };
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index ffd3463..ab9014e 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -28,13 +28,12 @@
 
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
 #include <ftl/unit.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/FrameRateMode.h>
-#include <utils/Trace.h>
 
 #include "RefreshRateSelector.h"
 
@@ -285,11 +284,12 @@
 
 std::string RefreshRateSelector::Policy::toString() const {
     return base::StringPrintf("{defaultModeId=%d, allowGroupSwitching=%s"
-                              ", primaryRanges=%s, appRequestRanges=%s}",
+                              ", primaryRanges=%s, appRequestRanges=%s idleScreenConfig=%s}",
                               ftl::to_underlying(defaultMode),
                               allowGroupSwitching ? "true" : "false",
-                              to_string(primaryRanges).c_str(),
-                              to_string(appRequestRanges).c_str());
+                              to_string(primaryRanges).c_str(), to_string(appRequestRanges).c_str(),
+                              idleScreenConfigOpt ? idleScreenConfigOpt->toString().c_str()
+                                                  : "nullptr");
 }
 
 std::pair<nsecs_t, nsecs_t> RefreshRateSelector::getDisplayFrames(nsecs_t layerPeriod,
@@ -474,38 +474,60 @@
 }
 
 auto RefreshRateSelector::getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                                              GlobalSignals signals) const -> RankedFrameRates {
+                                              GlobalSignals signals, Fps pacesetterFps) const
+        -> RankedFrameRates {
+    GetRankedFrameRatesCache cache{layers, signals, pacesetterFps};
+
     std::lock_guard lock(mLock);
 
-    if (mGetRankedFrameRatesCache &&
-        mGetRankedFrameRatesCache->arguments == std::make_pair(layers, signals)) {
+    if (mGetRankedFrameRatesCache && mGetRankedFrameRatesCache->matches(cache)) {
         return mGetRankedFrameRatesCache->result;
     }
 
-    const auto result = getRankedFrameRatesLocked(layers, signals);
-    mGetRankedFrameRatesCache = GetRankedFrameRatesCache{{layers, signals}, result};
-    return result;
+    cache.result = getRankedFrameRatesLocked(layers, signals, pacesetterFps);
+    mGetRankedFrameRatesCache = std::move(cache);
+    return mGetRankedFrameRatesCache->result;
 }
 
 auto RefreshRateSelector::getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                                    GlobalSignals signals) const
+                                                    GlobalSignals signals, Fps pacesetterFps) const
         -> RankedFrameRates {
     using namespace fps_approx_ops;
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("%s: %zu layers", __func__, layers.size());
 
     const auto& activeMode = *getActiveModeLocked().modePtr;
 
+    if (pacesetterFps.isValid()) {
+        ALOGV("Follower display");
+
+        const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending,
+                                            std::nullopt, [&](FrameRateMode mode) {
+                                                return mode.modePtr->getPeakFps() == pacesetterFps;
+                                            });
+
+        if (!ranking.empty()) {
+            SFTRACE_FORMAT_INSTANT("%s (Follower display)",
+                                   to_string(ranking.front().frameRateMode.fps).c_str());
+
+            return {ranking, kNoSignals, pacesetterFps};
+        }
+
+        ALOGW("Follower display cannot follow the pacesetter");
+    }
+
     // Keep the display at max frame rate for the duration of powering on the display.
     if (signals.powerOnImminent) {
         ALOGV("Power On Imminent");
         const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (Power On Imminent)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Power On Imminent)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.powerOnImminent = true}};
     }
 
     int noVoteLayers = 0;
+    // Layers that prefer the same mode ("no-op").
+    int noPreferenceLayers = 0;
     int minVoteLayers = 0;
     int maxVoteLayers = 0;
     int explicitDefaultVoteLayers = 0;
@@ -549,10 +571,7 @@
                     explicitCategoryVoteLayers++;
                 }
                 if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
-                    // Count this layer for Min vote as well. The explicit vote avoids
-                    // touch boost and idle for choosing a category, while Min vote is for correct
-                    // behavior when all layers are Min or no vote.
-                    minVoteLayers++;
+                    noPreferenceLayers++;
                 }
                 break;
             case LayerVoteType::Heuristic:
@@ -588,8 +607,8 @@
     if (signals.touch && !hasExplicitVoteLayers) {
         ALOGV("Touch Boost");
         const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (Touch Boost)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Touch Boost)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.touch = true}};
     }
 
@@ -600,18 +619,29 @@
         !(policy->primaryRangeIsSingleRate() && hasExplicitVoteLayers)) {
         ALOGV("Idle");
         const auto ranking = rankFrameRates(activeMode.getGroup(), RefreshRateOrder::Ascending);
-        ATRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (Idle)", to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, GlobalSignals{.idle = true}};
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
         ALOGV("No layers with votes");
         const auto ranking = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-        ATRACE_FORMAT_INSTANT("%s (No layers with votes)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (No layers with votes)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
     }
 
+    // If all layers are category NoPreference, use the current config.
+    if (noPreferenceLayers + noVoteLayers == layers.size()) {
+        ALOGV("All layers NoPreference");
+        constexpr float kScore = std::numeric_limits<float>::max();
+        FrameRateRanking currentMode;
+        currentMode.emplace_back(ScoredFrameRate{getActiveModeLocked(), kScore});
+        SFTRACE_FORMAT_INSTANT("%s (All layers NoPreference)",
+                              to_string(currentMode.front().frameRateMode.fps).c_str());
+        return {currentMode, kNoSignals};
+    }
+
     const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
     const DisplayModeId activeModeId = activeMode.getId();
 
@@ -623,8 +653,8 @@
                                                 return !smoothSwitchOnly ||
                                                         mode.modePtr->getId() == activeModeId;
                                             });
-        ATRACE_FORMAT_INSTANT("%s (All layers Min)",
-                              to_string(ranking.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (All layers Min)",
+                               to_string(ranking.front().frameRateMode.fps).c_str());
         return {ranking, kNoSignals};
     }
 
@@ -643,6 +673,7 @@
               ftl::enum_string(layer.frameRateCategory).c_str());
         if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
             layer.vote == LayerVoteType::Min) {
+            ALOGV("%s scoring skipped due to vote", formatLayerInfo(layer, layer.weight).c_str());
             continue;
         }
 
@@ -810,19 +841,20 @@
         return score.overallScore == 0;
     });
 
-    if (policy->primaryRangeIsSingleRate()) {
+    // TODO(b/364651864): Evaluate correctness of primaryRangeIsSingleRate.
+    if (!mIsVrrDevice.load() && policy->primaryRangeIsSingleRate()) {
         // If we never scored any layers, then choose the rate from the primary
         // range instead of picking a random score from the app range.
         if (noLayerScore) {
             ALOGV("Layers not scored");
             const auto descending = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-            ATRACE_FORMAT_INSTANT("%s (Layers not scored)",
-                                  to_string(descending.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (Layers not scored)",
+                                   to_string(descending.front().frameRateMode.fps).c_str());
             return {descending, kNoSignals};
         } else {
             ALOGV("primaryRangeIsSingleRate");
-            ATRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
-                                  to_string(ranking.front().frameRateMode.fps).c_str());
+            SFTRACE_FORMAT_INSTANT("%s (primaryRangeIsSingleRate)",
+                                   to_string(ranking.front().frameRateMode.fps).c_str());
             return {ranking, kNoSignals};
         }
     }
@@ -831,18 +863,19 @@
     // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
-    const bool touchBoostForExplicitExact = [&] {
+    const auto isTouchBoostForExplicitExact = [&]() -> bool {
         if (supportsAppFrameRateOverrideByContent()) {
             // Enable touch boost if there are other layers besides exact
-            return explicitExact + noVoteLayers != layers.size();
+            return explicitExact + noVoteLayers + explicitGteLayers != layers.size();
         } else {
             // Enable touch boost if there are no exact layers
             return explicitExact == 0;
         }
-    }();
+    };
 
-    const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
-    using fps_approx_ops::operator<;
+    const auto isTouchBoostForCategory = [&]() -> bool {
+        return explicitCategoryVoteLayers + noVoteLayers + explicitGteLayers != layers.size();
+    };
 
     // A method for UI Toolkit to send the touch signal via "HighHint" category vote,
     // which will touch boost when there are no ExplicitDefault layer votes. This is an
@@ -850,12 +883,17 @@
     // compatibility to limit the frame rate, which should not have touch boost.
     const bool hasInteraction = signals.touch || interactiveLayers > 0;
 
-    if (hasInteraction && explicitDefaultVoteLayers == 0 && touchBoostForExplicitExact &&
-        scores.front().frameRateMode.fps < touchRefreshRates.front().frameRateMode.fps) {
-        ALOGV("Touch Boost");
-        ATRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
-                              to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
-        return {touchRefreshRates, GlobalSignals{.touch = true}};
+    if (hasInteraction && explicitDefaultVoteLayers == 0 && isTouchBoostForExplicitExact() &&
+        isTouchBoostForCategory()) {
+        const auto touchRefreshRates = rankFrameRates(anchorGroup, RefreshRateOrder::Descending);
+        using fps_approx_ops::operator<;
+
+        if (scores.front().frameRateMode.fps <= touchRefreshRates.front().frameRateMode.fps) {
+            ALOGV("Touch Boost [late]");
+            SFTRACE_FORMAT_INSTANT("%s (Touch Boost [late])",
+                                   to_string(touchRefreshRates.front().frameRateMode.fps).c_str());
+            return {touchRefreshRates, GlobalSignals{.touch = true}};
+        }
     }
 
     // If we never scored any layers, and we don't favor high refresh rates, prefer to stay with the
@@ -864,13 +902,13 @@
         ALOGV("preferredDisplayMode");
         const auto ascendingWithPreferred =
                 rankFrameRates(anchorGroup, RefreshRateOrder::Ascending, activeMode.getId());
-        ATRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
-                              to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
+        SFTRACE_FORMAT_INSTANT("%s (preferredDisplayMode)",
+                               to_string(ascendingWithPreferred.front().frameRateMode.fps).c_str());
         return {ascendingWithPreferred, kNoSignals};
     }
 
-    ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
-    ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
+    ALOGV("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
+    SFTRACE_FORMAT_INSTANT("%s (scored)", to_string(ranking.front().frameRateMode.fps).c_str());
     return {ranking, kNoSignals};
 }
 
@@ -912,7 +950,7 @@
                                                 Fps displayRefreshRate,
                                                 GlobalSignals globalSignals) const
         -> UidToFrameRateOverride {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mConfig.enableFrameRateOverride == Config::FrameRateOverride::Disabled) {
         return {};
     }
@@ -1027,8 +1065,13 @@
                                       return lhs < rhs && !ScoredFrameRate::scoresEqual(lhs, rhs);
                                   });
         ALOGV("%s: overriding to %s for uid=%d", __func__, to_string(overrideFps).c_str(), uid);
-        ATRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
-                              to_string(overrideFps).c_str(), uid);
+        SFTRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
+                               to_string(overrideFps).c_str(), uid);
+        if (SFTRACE_ENABLED() && FlagManager::getInstance().trace_frame_rate_override()) {
+            std::stringstream ss;
+            ss << "FrameRateOverride " << uid;
+            SFTRACE_INT(ss.str().c_str(), overrideFps.getIntValue());
+        }
         frameRateOverrides.emplace(uid, overrideFps);
     }
 
@@ -1212,19 +1255,21 @@
     LOG_ALWAYS_FATAL_IF(!activeModeOpt);
 
     mActiveModeOpt.emplace(FrameRateMode{renderFrameRate, ftl::as_non_null(activeModeOpt->get())});
+    mIsVrrDevice = FlagManager::getInstance().vrr_config() &&
+            activeModeOpt->get()->getVrrConfig().has_value();
 }
 
 RefreshRateSelector::RefreshRateSelector(DisplayModes modes, DisplayModeId activeModeId,
                                          Config config)
       : mKnownFrameRates(constructKnownFrameRates(modes)), mConfig(config) {
-    initializeIdleTimer();
+    initializeIdleTimer(mConfig.legacyIdleTimerTimeout);
     FTL_FAKE_GUARD(kMainThreadContext, updateDisplayModes(std::move(modes), activeModeId));
 }
 
-void RefreshRateSelector::initializeIdleTimer() {
-    if (mConfig.idleTimerTimeout > 0ms) {
+void RefreshRateSelector::initializeIdleTimer(std::chrono::milliseconds timeout) {
+    if (timeout > 0ms) {
         mIdleTimer.emplace(
-                "IdleTimer", mConfig.idleTimerTimeout,
+                "IdleTimer", timeout,
                 [this] {
                     std::scoped_lock lock(mIdleTimerCallbacksMutex);
                     if (const auto callbacks = getIdleTimerCallbacks()) {
@@ -1347,9 +1392,41 @@
 
         mGetRankedFrameRatesCache.reset();
 
-        if (*getCurrentPolicyLocked() == oldPolicy) {
+        const auto& idleScreenConfigOpt = getCurrentPolicyLocked()->idleScreenConfigOpt;
+        if (idleScreenConfigOpt != oldPolicy.idleScreenConfigOpt) {
+            if (!idleScreenConfigOpt.has_value()) {
+                if (mIdleTimer) {
+                    // fallback to legacy timer if existed, otherwise pause the old timer
+                    if (mConfig.legacyIdleTimerTimeout > 0ms) {
+                        mIdleTimer->setInterval(mConfig.legacyIdleTimerTimeout);
+                        mIdleTimer->resume();
+                    } else {
+                        mIdleTimer->pause();
+                    }
+                }
+            } else if (idleScreenConfigOpt->timeoutMillis > 0) {
+                // create a new timer or reconfigure
+                const auto timeout = std::chrono::milliseconds{idleScreenConfigOpt->timeoutMillis};
+                if (!mIdleTimer) {
+                    initializeIdleTimer(timeout);
+                    if (mIdleTimerStarted) {
+                        mIdleTimer->start();
+                    }
+                } else {
+                    mIdleTimer->setInterval(timeout);
+                    mIdleTimer->resume();
+                }
+            } else {
+                if (mIdleTimer) {
+                    mIdleTimer->pause();
+                }
+            }
+        }
+
+        if (getCurrentPolicyLocked()->similarExceptIdleConfig(oldPolicy)) {
             return SetPolicyResult::Unchanged;
         }
+
         constructAvailableRefreshRates();
 
         displayId = getActiveModeLocked().modePtr->getPhysicalDisplayId();
@@ -1425,7 +1502,8 @@
             }
             return str;
         };
-        ALOGV("%s render rates: %s", rangeName, stringifyModes().c_str());
+        ALOGV("%s render rates: %s, isVrrDevice? %d", rangeName, stringifyModes().c_str(),
+              mIsVrrDevice.load());
 
         return frameRateModes;
     };
@@ -1434,6 +1512,10 @@
     mAppRequestFrameRates = filterRefreshRates(policy->appRequestRanges, "app request");
 }
 
+bool RefreshRateSelector::isVrrDevice() const {
+    return mIsVrrDevice;
+}
+
 Fps RefreshRateSelector::findClosestKnownFrameRate(Fps frameRate) const {
     using namespace fps_approx_ops;
 
@@ -1514,7 +1596,8 @@
     std::lock_guard lock(mLock);
 
     const auto activeMode = getActiveModeLocked();
-    dumper.dump("activeMode"sv, to_string(activeMode));
+    dumper.dump("renderRate"sv, to_string(activeMode.fps));
+    dumper.dump("activeMode"sv, to_string(*activeMode.modePtr));
 
     dumper.dump("displayModes"sv);
     {
@@ -1545,7 +1628,10 @@
 }
 
 std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
-    return mConfig.idleTimerTimeout;
+    if (FlagManager::getInstance().idle_screen_refresh_rate_timeout() && mIdleTimer) {
+        return std::chrono::duration_cast<std::chrono::milliseconds>(mIdleTimer->interval());
+    }
+    return mConfig.legacyIdleTimerTimeout;
 }
 
 // TODO(b/293651105): Extract category FpsRange mapping to OEM-configurable config.
@@ -1554,19 +1640,17 @@
         case FrameRateCategory::High:
             return FpsRange{90_Hz, 120_Hz};
         case FrameRateCategory::Normal:
-            return FpsRange{60_Hz, 90_Hz};
+            return FpsRange{60_Hz, 120_Hz};
         case FrameRateCategory::Low:
-            return FpsRange{30_Hz, 30_Hz};
+            return FpsRange{48_Hz, 120_Hz};
         case FrameRateCategory::HighHint:
         case FrameRateCategory::NoPreference:
         case FrameRateCategory::Default:
             LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
                              ftl::enum_string(category).c_str());
-            return FpsRange{0_Hz, 0_Hz};
         default:
             LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s",
                              ftl::enum_string(category).c_str());
-            return FpsRange{0_Hz, 0_Hz};
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index e8153f5..a398c01 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -67,26 +67,32 @@
         FpsRanges primaryRanges;
         // The app request refresh rate ranges. @see DisplayModeSpecs.aidl for details.
         FpsRanges appRequestRanges;
+        // The idle timer configuration, if provided.
+        std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig> idleScreenConfigOpt;
 
         Policy() = default;
 
         Policy(DisplayModeId defaultMode, FpsRange range,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
-                       allowGroupSwitching) {}
+                       allowGroupSwitching, idleScreenConfigOpt) {}
 
         Policy(DisplayModeId defaultMode, FpsRanges primaryRanges, FpsRanges appRequestRanges,
-               bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+               bool allowGroupSwitching = kAllowGroupSwitchingDefault,
+               const std::optional<gui::DisplayModeSpecs::IdleScreenRefreshRateConfig>&
+                       idleScreenConfigOpt = std::nullopt)
               : defaultMode(defaultMode),
                 allowGroupSwitching(allowGroupSwitching),
                 primaryRanges(primaryRanges),
-                appRequestRanges(appRequestRanges) {}
+                appRequestRanges(appRequestRanges),
+                idleScreenConfigOpt(idleScreenConfigOpt) {}
 
         bool operator==(const Policy& other) const {
             using namespace fps_approx_ops;
-            return defaultMode == other.defaultMode && primaryRanges == other.primaryRanges &&
-                    appRequestRanges == other.appRequestRanges &&
-                    allowGroupSwitching == other.allowGroupSwitching;
+            return similarExceptIdleConfig(other) &&
+                    idleScreenConfigOpt == other.idleScreenConfigOpt;
         }
 
         bool operator!=(const Policy& other) const { return !(*this == other); }
@@ -95,6 +101,13 @@
             return isApproxEqual(primaryRanges.physical.min, primaryRanges.physical.max);
         }
 
+        bool similarExceptIdleConfig(const Policy& updated) const {
+            using namespace fps_approx_ops;
+            return defaultMode == updated.defaultMode && primaryRanges == updated.primaryRanges &&
+                    appRequestRanges == updated.appRequestRanges &&
+                    allowGroupSwitching == updated.allowGroupSwitching;
+        }
+
         std::string toString() const;
     };
 
@@ -197,6 +210,8 @@
         // within the timeout of DisplayPowerTimer.
         bool powerOnImminent = false;
 
+        bool shouldEmitEvent() const { return !idle; }
+
         bool operator==(GlobalSignals other) const {
             return touch == other.touch && idle == other.idle &&
                     powerOnImminent == other.powerOnImminent;
@@ -233,14 +248,18 @@
     struct RankedFrameRates {
         FrameRateRanking ranking; // Ordered by descending score.
         GlobalSignals consideredSignals;
+        Fps pacesetterFps;
 
         bool operator==(const RankedFrameRates& other) const {
-            return ranking == other.ranking && consideredSignals == other.consideredSignals;
+            return ranking == other.ranking && consideredSignals == other.consideredSignals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
         }
     };
 
-    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals) const
-            EXCLUDES(mLock);
+    // If valid, `pacesetterFps` (used by follower displays) filters the ranking to modes matching
+    // that refresh rate.
+    RankedFrameRates getRankedFrameRates(const std::vector<LayerRequirement>&, GlobalSignals,
+                                         Fps pacesetterFps = {}) const EXCLUDES(mLock);
 
     FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
         std::lock_guard lock(mLock);
@@ -287,7 +306,7 @@
         int frameRateMultipleThreshold = 0;
 
         // The Idle Timer timeout. 0 timeout means no idle timer.
-        std::chrono::milliseconds idleTimerTimeout = 0ms;
+        std::chrono::milliseconds legacyIdleTimerTimeout = 0ms;
 
         // The controller representing how the kernel idle timer will be configured
         // either on the HWC api or sysprop.
@@ -298,7 +317,7 @@
             DisplayModes, DisplayModeId activeModeId,
             Config config = {.enableFrameRateOverride = Config::FrameRateOverride::Disabled,
                              .frameRateMultipleThreshold = 0,
-                             .idleTimerTimeout = 0ms,
+                             .legacyIdleTimerTimeout = 0ms,
                              .kernelIdleTimerController = {}});
 
     RefreshRateSelector(const RefreshRateSelector&) = delete;
@@ -369,6 +388,7 @@
 
         Callbacks platform;
         Callbacks kernel;
+        Callbacks vrr;
     };
 
     void setIdleTimerCallbacks(IdleTimerCallbacks callbacks) EXCLUDES(mIdleTimerCallbacksMutex) {
@@ -382,12 +402,14 @@
     }
 
     void startIdleTimer() {
+        mIdleTimerStarted = true;
         if (mIdleTimer) {
             mIdleTimer->start();
         }
     }
 
     void stopIdleTimer() {
+        mIdleTimerStarted = false;
         if (mIdleTimer) {
             mIdleTimer->stop();
         }
@@ -409,6 +431,8 @@
 
     std::chrono::milliseconds getIdleTimerTimeout();
 
+    bool isVrrDevice() const;
+
 private:
     friend struct TestableRefreshRateSelector;
 
@@ -418,7 +442,8 @@
     const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
 
     RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& layers,
-                                               GlobalSignals signals) const REQUIRES(mLock);
+                                               GlobalSignals signals, Fps pacesetterFps) const
+            REQUIRES(mLock);
 
     // Returns number of display frames and remainder when dividing the layer refresh period by
     // display refresh period.
@@ -477,11 +502,14 @@
     void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
             REQUIRES(kMainThreadContext);
 
-    void initializeIdleTimer();
+    void initializeIdleTimer(std::chrono::milliseconds timeout);
 
     std::optional<IdleTimerCallbacks::Callbacks> getIdleTimerCallbacks() const
             REQUIRES(mIdleTimerCallbacksMutex) {
         if (!mIdleTimerCallbacks) return {};
+
+        if (mIsVrrDevice) return mIdleTimerCallbacks->vrr;
+
         return mConfig.kernelIdleTimerController.has_value() ? mIdleTimerCallbacks->kernel
                                                              : mIdleTimerCallbacks->platform;
     }
@@ -516,6 +544,9 @@
     std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
     std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
 
+    // Caches whether the device is VRR-compatible based on the active display mode.
+    std::atomic_bool mIsVrrDevice = false;
+
     Policy mDisplayManagerPolicy GUARDED_BY(mLock);
     std::optional<Policy> mOverridePolicy GUARDED_BY(mLock);
 
@@ -537,8 +568,16 @@
     Config::FrameRateOverride mFrameRateOverrideConfig;
 
     struct GetRankedFrameRatesCache {
-        std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
+        std::vector<LayerRequirement> layers;
+        GlobalSignals signals;
+        Fps pacesetterFps;
+
         RankedFrameRates result;
+
+        bool matches(const GetRankedFrameRatesCache& other) const {
+            return layers == other.layers && signals == other.signals &&
+                    isApproxEqual(pacesetterFps, other.pacesetterFps);
+        }
     };
     mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
 
@@ -547,6 +586,7 @@
     std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
     // Used to detect (lack of) frame activity.
     ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
+    std::atomic<bool> mIdleTimerStarted = false;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 3f91682..ee811eb 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -24,12 +24,12 @@
 #include <android-base/stringprintf.h>
 #include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
 #include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
+#include <common/trace.h>
 #include <configstore/Utils.h>
 #include <ftl/concat.h>
 #include <ftl/enum.h>
 #include <ftl/fake_guard.h>
 #include <ftl/small_map.h>
-#include <gui/TraceUtils.h>
 #include <gui/WindowInfo.h>
 #include <system/window.h>
 #include <ui/DisplayMap.h>
@@ -38,7 +38,6 @@
 #include <FrameTimeline/FrameTimeline.h>
 #include <scheduler/interface/ICompositor.h>
 
-#include <algorithm>
 #include <cinttypes>
 #include <cstdint>
 #include <functional>
@@ -46,16 +45,15 @@
 #include <numeric>
 
 #include <common/FlagManager.h>
-#include "../Layer.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
 #include "OneShotTimer.h"
 #include "RefreshRateStats.h"
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
-#include "VSyncTracker.h"
 #include "VsyncConfiguration.h"
 #include "VsyncController.h"
 #include "VsyncSchedule.h"
@@ -82,7 +80,7 @@
     mTouchTimer.reset();
 
     // Stop idle timer and clear callbacks, as the RefreshRateSelector may outlive the Scheduler.
-    demotePacesetterDisplay();
+    demotePacesetterDisplay({.toggleIdleTimer = true});
 }
 
 void Scheduler::initVsync(frametimeline::TokenManager& tokenManager,
@@ -117,35 +115,51 @@
     }
 }
 
-void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
-    demotePacesetterDisplay();
+void Scheduler::setPacesetterDisplay(PhysicalDisplayId pacesetterId) {
+    constexpr PromotionParams kPromotionParams = {.toggleIdleTimer = true};
 
-    promotePacesetterDisplay(pacesetterIdOpt);
+    demotePacesetterDisplay(kPromotionParams);
+    promotePacesetterDisplay(pacesetterId, kPromotionParams);
+
+    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
+    mVsyncModulator->cancelRefreshRateChange();
+
+    mVsyncConfiguration->reset();
+    updatePhaseConfiguration(pacesetterId, pacesetterSelectorPtr()->getActiveMode().fps);
 }
 
-void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+                                PhysicalDisplayId activeDisplayId) {
     auto schedulePtr =
             std::make_shared<VsyncSchedule>(selectorPtr->getActiveMode().modePtr, mFeatures,
                                             [this](PhysicalDisplayId id, bool enable) {
                                                 onHardwareVsyncRequest(id, enable);
                                             });
 
-    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
+    registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr),
+                            activeDisplayId);
 }
 
 void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
                                         RefreshRateSelectorPtr selectorPtr,
-                                        VsyncSchedulePtr schedulePtr) {
-    demotePacesetterDisplay();
+                                        VsyncSchedulePtr schedulePtr,
+                                        PhysicalDisplayId activeDisplayId) {
+    const bool isPrimary = (ftl::FakeGuard(mDisplayLock), !mPacesetterDisplayId);
 
-    auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
+    // Start the idle timer for the first registered (i.e. primary) display.
+    const PromotionParams promotionParams = {.toggleIdleTimer = isPrimary};
+
+    demotePacesetterDisplay(promotionParams);
+
+    auto [pacesetterVsyncSchedule, isNew] = [&]() REQUIRES(kMainThreadContext) {
         std::scoped_lock lock(mDisplayLock);
         const bool isNew = mDisplays
                                    .emplace_or_replace(displayId, displayId, std::move(selectorPtr),
                                                        std::move(schedulePtr), mFeatures)
                                    .second;
 
-        return std::make_pair(promotePacesetterDisplayLocked(), isNew);
+        return std::make_pair(promotePacesetterDisplayLocked(activeDisplayId, promotionParams),
+                              isNew);
     }();
 
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
@@ -158,10 +172,13 @@
     dispatchHotplug(displayId, Hotplug::Connected);
 }
 
-void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId, PhysicalDisplayId activeDisplayId) {
+    LOG_ALWAYS_FATAL_IF(displayId == activeDisplayId, "Cannot unregister the active display!");
+
     dispatchHotplug(displayId, Hotplug::Disconnected);
 
-    demotePacesetterDisplay();
+    constexpr PromotionParams kPromotionParams = {.toggleIdleTimer = false};
+    demotePacesetterDisplay(kPromotionParams);
 
     std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
     {
@@ -173,7 +190,7 @@
         // headless virtual display.)
         LOG_ALWAYS_FATAL_IF(mDisplays.empty(), "Cannot unregister all displays!");
 
-        pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(activeDisplayId, kPromotionParams);
     }
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
@@ -221,6 +238,7 @@
             if (FlagManager::getInstance().vrr_config()) {
                 compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
             }
+            mSchedulerCallback.onCommitNotComposited();
             return;
         }
     }
@@ -244,24 +262,12 @@
         const auto period = pacesetterPtr->targeterPtr->target().expectedFrameDuration();
         const auto skipDuration = Duration::fromNs(
                 static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
-        ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
-                      mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+        SFTRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+                       mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
         std::this_thread::sleep_for(skipDuration);
         mPacesetterFrameDurationFractionToSkip = 0.f;
     }
 
-    if (FlagManager::getInstance().vrr_config()) {
-        const auto minFramePeriod = pacesetterPtr->schedulePtr->minFramePeriod();
-        const auto presentFenceForPastVsync =
-                pacesetterPtr->targeterPtr->target().presentFenceForPastVsync(minFramePeriod);
-        const auto lastConfirmedPresentTime = presentFenceForPastVsync->getSignalTime();
-        if (lastConfirmedPresentTime != Fence::SIGNAL_TIME_PENDING &&
-            lastConfirmedPresentTime != Fence::SIGNAL_TIME_INVALID) {
-            pacesetterPtr->schedulePtr->getTracker()
-                    .onFrameBegin(expectedVsyncTime, TimePoint::fromNs(lastConfirmedPresentTime));
-        }
-    }
-
     const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
     if (FlagManager::getInstance().vrr_config()) {
         compositor.sendNotifyExpectedPresentHint(pacesetterPtr->displayId);
@@ -288,7 +294,7 @@
         return true;
     }
 
-    ATRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
+    SFTRACE_FORMAT("%s uid: %d frameRate: %s", __func__, uid, to_string(*frameRate).c_str());
     return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), *frameRate);
 }
 
@@ -306,8 +312,11 @@
         const auto pacesetterOpt = pacesetterDisplayLocked();
         LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
         const Display& pacesetter = *pacesetterOpt;
-        return std::make_pair(pacesetter.selectorPtr->getActiveMode().fps,
-                              pacesetter.schedulePtr->period());
+        const FrameRateMode& frameRateMode = pacesetter.selectorPtr->getActiveMode();
+        const auto refreshRate = frameRateMode.fps;
+        const auto displayVsync = frameRateMode.modePtr->getVsyncRate();
+        const auto numPeriod = RefreshRateSelector::getFrameRateDivisor(displayVsync, refreshRate);
+        return std::make_pair(refreshRate, numPeriod * pacesetter.schedulePtr->period());
     }();
 
     const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
@@ -350,10 +359,8 @@
 
     if (cycle == Cycle::Render) {
         mRenderEventThread = std::move(eventThread);
-        mRenderEventConnection = mRenderEventThread->createEventConnection();
     } else {
         mLastCompositeEventThread = std::move(eventThread);
-        mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
     }
 }
 
@@ -398,14 +405,20 @@
     eventThreadFor(Cycle::Render).enableSyntheticVsync(enable);
 }
 
-void Scheduler::onFrameRateOverridesChanged(Cycle cycle, PhysicalDisplayId displayId) {
-    const bool supportsFrameRateOverrideByContent =
-            pacesetterSelectorPtr()->supportsAppFrameRateOverrideByContent();
+void Scheduler::onFrameRateOverridesChanged() {
+    const auto [pacesetterId, supportsFrameRateOverrideByContent] = [this] {
+        std::scoped_lock lock(mDisplayLock);
+        const auto pacesetterOpt = pacesetterDisplayLocked();
+        LOG_ALWAYS_FATAL_IF(!pacesetterOpt);
+        const Display& pacesetter = *pacesetterOpt;
+        return std::make_pair(FTL_FAKE_GUARD(kMainThreadContext, *mPacesetterDisplayId),
+                              pacesetter.selectorPtr->supportsAppFrameRateOverrideByContent());
+    }();
 
     std::vector<FrameRateOverride> overrides =
             mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
 
-    eventThreadFor(cycle).onFrameRateOverridesChanged(displayId, std::move(overrides));
+    eventThreadFor(Cycle::Render).onFrameRateOverridesChanged(pacesetterId, std::move(overrides));
 }
 
 void Scheduler::onHdcpLevelsChanged(Cycle cycle, PhysicalDisplayId displayId,
@@ -413,50 +426,52 @@
     eventThreadFor(cycle).onHdcpLevelsChanged(displayId, connectedLevel, maxLevel);
 }
 
-void Scheduler::onPrimaryDisplayModeChanged(Cycle cycle, const FrameRateMode& mode) {
-    {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
+bool Scheduler::onDisplayModeChanged(PhysicalDisplayId displayId, const FrameRateMode& mode) {
+    const bool isPacesetter =
+            FTL_FAKE_GUARD(kMainThreadContext,
+                           (std::scoped_lock(mDisplayLock), displayId == mPacesetterDisplayId));
+
+    if (isPacesetter) {
         std::lock_guard<std::mutex> lock(mPolicyLock);
-        // Cache the last reported modes for primary display.
-        mPolicy.cachedModeChangedParams = {cycle, mode};
+        mPolicy.emittedModeOpt = mode;
 
         // Invalidate content based refresh rate selection so it could be calculated
         // again for the new refresh rate.
         mPolicy.contentRequirements.clear();
     }
-    onNonPrimaryDisplayModeChanged(cycle, mode);
-}
 
-void Scheduler::dispatchCachedReportedMode() {
-    // Check optional fields first.
-    if (!mPolicy.modeOpt) {
-        ALOGW("No mode ID found, not dispatching cached mode.");
-        return;
-    }
-    if (!mPolicy.cachedModeChangedParams) {
-        ALOGW("No mode changed params found, not dispatching cached mode.");
-        return;
-    }
-
-    // If the mode is not the current mode, this means that a
-    // mode change is in progress. In that case we shouldn't dispatch an event
-    // as it will be dispatched when the current mode changes.
-    if (pacesetterSelectorPtr()->getActiveMode() != mPolicy.modeOpt) {
-        return;
-    }
-
-    // If there is no change from cached mode, there is no need to dispatch an event
-    if (*mPolicy.modeOpt == mPolicy.cachedModeChangedParams->mode) {
-        return;
-    }
-
-    mPolicy.cachedModeChangedParams->mode = *mPolicy.modeOpt;
-    onNonPrimaryDisplayModeChanged(mPolicy.cachedModeChangedParams->cycle,
-                                   mPolicy.cachedModeChangedParams->mode);
-}
-
-void Scheduler::onNonPrimaryDisplayModeChanged(Cycle cycle, const FrameRateMode& mode) {
     if (hasEventThreads()) {
-        eventThreadFor(cycle).onModeChanged(mode);
+        eventThreadFor(Cycle::Render).onModeChanged(mode);
+    }
+
+    return isPacesetter;
+}
+#pragma clang diagnostic pop
+
+void Scheduler::emitModeChangeIfNeeded() {
+    if (!mPolicy.modeOpt || !mPolicy.emittedModeOpt) {
+        ALOGW("No mode change to emit");
+        return;
+    }
+
+    const auto& mode = *mPolicy.modeOpt;
+
+    if (mode != pacesetterSelectorPtr()->getActiveMode()) {
+        // A mode change is pending. The event will be emitted when the mode becomes active.
+        return;
+    }
+
+    if (mode == *mPolicy.emittedModeOpt) {
+        // The event was already emitted.
+        return;
+    }
+
+    mPolicy.emittedModeOpt = mode;
+
+    if (hasEventThreads()) {
+        eventThreadFor(Cycle::Render).onModeChanged(mode);
     }
 }
 
@@ -471,20 +486,20 @@
     }
 }
 
-void Scheduler::updatePhaseConfiguration(Fps refreshRate) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
+void Scheduler::updatePhaseConfiguration(PhysicalDisplayId displayId, Fps refreshRate) {
+    const bool isPacesetter =
+            FTL_FAKE_GUARD(kMainThreadContext,
+                           (std::scoped_lock(mDisplayLock), displayId == mPacesetterDisplayId));
+    if (!isPacesetter) return;
+
     mRefreshRateStats->setRefreshRate(refreshRate);
     mVsyncConfiguration->setRefreshRateFps(refreshRate);
     setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()),
                    refreshRate.getPeriod());
 }
-
-void Scheduler::resetPhaseConfiguration(Fps refreshRate) {
-    // Cancel the pending refresh rate change, if any, before updating the phase configuration.
-    mVsyncModulator->cancelRefreshRateChange();
-
-    mVsyncConfiguration->reset();
-    updatePhaseConfiguration(refreshRate);
-}
+#pragma clang diagnostic pop
 
 void Scheduler::setActiveDisplayPowerModeForRefreshRateStats(hal::PowerMode powerMode) {
     mRefreshRateStats->setPowerMode(powerMode);
@@ -513,7 +528,7 @@
 }
 
 void Scheduler::resyncAllToHardwareVsync(bool allowToEnable) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
@@ -547,12 +562,12 @@
 
 void Scheduler::onHardwareVsyncRequest(PhysicalDisplayId id, bool enabled) {
     static const auto& whence = __func__;
-    ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+    SFTRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
     // On main thread to serialize reads/writes of pending hardware VSYNC state.
     static_cast<void>(
             schedule([=, this]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+                SFTRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
 
                 if (const auto displayOpt = mDisplays.get(id)) {
                     auto& display = displayOpt->get();
@@ -565,7 +580,7 @@
             }));
 }
 
-void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate) {
+void Scheduler::setRenderRate(PhysicalDisplayId id, Fps renderFrameRate, bool applyImmediately) {
     std::scoped_lock lock(mDisplayLock);
     ftl::FakeGuard guard(kMainThreadContext);
 
@@ -586,7 +601,7 @@
     ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
           to_string(mode.modePtr->getVsyncRate()).c_str());
 
-    display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
+    display.schedulePtr->getTracker().setRenderRate(renderFrameRate, applyImmediately);
 }
 
 Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id,
@@ -634,7 +649,7 @@
 }
 
 void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
-    ATRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
     const auto scheduleOpt =
             (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
                 return display.powerMode == hal::PowerMode::OFF
@@ -654,11 +669,12 @@
     }
 }
 
-void Scheduler::registerLayer(Layer* layer) {
+void Scheduler::registerLayer(Layer* layer, FrameRateCompatibility frameRateCompatibility) {
     // If the content detection feature is off, we still keep the layer history,
     // since we use it for other features (like Frame Rate API), so layers
     // still need to be registered.
-    mLayerHistory.registerLayer(layer, mFeatures.test(Feature::kContentDetection));
+    mLayerHistory.registerLayer(layer, mFeatures.test(Feature::kContentDetection),
+                                frameRateCompatibility);
 }
 
 void Scheduler::deregisterLayer(Layer* layer) {
@@ -697,7 +713,7 @@
     const auto selectorPtr = pacesetterSelectorPtr();
     if (!selectorPtr->canSwitch()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     LayerHistory::Summary summary = mLayerHistory.summarize(*selectorPtr, systemTime());
     applyPolicy(&Policy::contentRequirements, std::move(summary));
@@ -782,7 +798,7 @@
 }
 
 void Scheduler::kernelIdleTimerCallback(TimerState state) {
-    ATRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredKernelIdleTimer", static_cast<int>(state));
 
     // TODO(145561154): cleanup the kernel idle timer implementation and the refresh rate
     // magic number
@@ -813,7 +829,7 @@
 
 void Scheduler::idleTimerCallback(TimerState state) {
     applyPolicy(&Policy::idleTimer, state);
-    ATRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredIdleTimer", static_cast<int>(state));
 }
 
 void Scheduler::touchTimerCallback(TimerState state) {
@@ -825,12 +841,12 @@
     if (applyPolicy(&Policy::touch, touch).touch) {
         mLayerHistory.clear();
     }
-    ATRACE_INT("TouchState", static_cast<int>(touch));
+    SFTRACE_INT("TouchState", static_cast<int>(touch));
 }
 
 void Scheduler::displayPowerTimerCallback(TimerState state) {
     applyPolicy(&Policy::displayPowerTimer, state);
-    ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
+    SFTRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
 }
 
 void Scheduler::dump(utils::Dumper& dumper) const {
@@ -866,22 +882,19 @@
     mRefreshRateStats->dump(dumper.out());
     dumper.eol();
 
-    {
-        utils::Dumper::Section section(dumper, "Frame Targeting"sv);
+    std::scoped_lock lock(mDisplayLock);
+    ftl::FakeGuard guard(kMainThreadContext);
 
-        std::scoped_lock lock(mDisplayLock);
-        ftl::FakeGuard guard(kMainThreadContext);
+    for (const auto& [id, display] : mDisplays) {
+        utils::Dumper::Section
+                section(dumper,
+                        id == mPacesetterDisplayId
+                                ? ftl::Concat("Pacesetter Display ", id.value).c_str()
+                                : ftl::Concat("Follower Display ", id.value).c_str());
 
-        for (const auto& [id, display] : mDisplays) {
-            utils::Dumper::Section
-                    section(dumper,
-                            id == mPacesetterDisplayId
-                                    ? ftl::Concat("Pacesetter Display ", id.value).c_str()
-                                    : ftl::Concat("Follower Display ", id.value).c_str());
-
-            display.targeterPtr->dump(dumper);
-            dumper.eol();
-        }
+        display.selectorPtr->dump(dumper);
+        display.targeterPtr->dump(dumper);
+        dumper.eol();
     }
 }
 
@@ -902,10 +915,17 @@
     }
 }
 
-bool Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
-    std::scoped_lock lock(mPolicyLock);
-    return updateFrameRateOverridesLocked(consideredSignals, displayRefreshRate);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
+void Scheduler::updateFrameRateOverrides(GlobalSignals consideredSignals, Fps displayRefreshRate) {
+    const bool changed = (std::scoped_lock(mPolicyLock),
+                          updateFrameRateOverridesLocked(consideredSignals, displayRefreshRate));
+
+    if (changed) {
+        onFrameRateOverridesChanged();
+    }
 }
+#pragma clang diagnostic pop
 
 bool Scheduler::updateFrameRateOverridesLocked(GlobalSignals consideredSignals,
                                                Fps displayRefreshRate) {
@@ -920,35 +940,38 @@
     return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
 }
 
-void Scheduler::promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+void Scheduler::promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams params) {
     std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
-
     {
         std::scoped_lock lock(mDisplayLock);
-        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt);
+        pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterId, params);
     }
 
     applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
 }
 
 std::shared_ptr<VsyncSchedule> Scheduler::promotePacesetterDisplayLocked(
-        std::optional<PhysicalDisplayId> pacesetterIdOpt) {
-    // TODO(b/241286431): Choose the pacesetter display.
-    mPacesetterDisplayId = pacesetterIdOpt.value_or(mDisplays.begin()->first);
-    ALOGI("Display %s is the pacesetter", to_string(*mPacesetterDisplayId).c_str());
+        PhysicalDisplayId pacesetterId, PromotionParams params) {
+    // TODO: b/241286431 - Choose the pacesetter among mDisplays.
+    mPacesetterDisplayId = pacesetterId;
+    ALOGI("Display %s is the pacesetter", to_string(pacesetterId).c_str());
 
     std::shared_ptr<VsyncSchedule> newVsyncSchedulePtr;
     if (const auto pacesetterOpt = pacesetterDisplayLocked()) {
         const Display& pacesetter = *pacesetterOpt;
 
-        pacesetter.selectorPtr->setIdleTimerCallbacks(
-                {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
-                              .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
-                 .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
-                            .onExpired =
-                                    [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+        if (!FlagManager::getInstance().connected_display() || params.toggleIdleTimer) {
+            pacesetter.selectorPtr->setIdleTimerCallbacks(
+                    {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
+                                  .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
+                     .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
+                                .onExpired =
+                                        [this] { kernelIdleTimerCallback(TimerState::Expired); }},
+                     .vrr = {.onReset = [this] { mSchedulerCallback.vrrDisplayIdle(false); },
+                             .onExpired = [this] { mSchedulerCallback.vrrDisplayIdle(true); }}});
 
-        pacesetter.selectorPtr->startIdleTimer();
+            pacesetter.selectorPtr->startIdleTimer();
+        }
 
         newVsyncSchedulePtr = pacesetter.schedulePtr;
 
@@ -968,11 +991,14 @@
     }
 }
 
-void Scheduler::demotePacesetterDisplay() {
-    // No need to lock for reads on kMainThreadContext.
-    if (const auto pacesetterPtr = FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) {
-        pacesetterPtr->stopIdleTimer();
-        pacesetterPtr->clearIdleTimerCallbacks();
+void Scheduler::demotePacesetterDisplay(PromotionParams params) {
+    if (!FlagManager::getInstance().connected_display() || params.toggleIdleTimer) {
+        // No need to lock for reads on kMainThreadContext.
+        if (const auto pacesetterPtr =
+                    FTL_FAKE_GUARD(mDisplayLock, pacesetterSelectorPtrLocked())) {
+            pacesetterPtr->stopIdleTimer();
+            pacesetterPtr->clearIdleTimerCallbacks();
+        }
     }
 
     // Clear state that depends on the pacesetter's RefreshRateSelector.
@@ -993,7 +1019,7 @@
     auto& layerChoreographers = choreographers->second;
 
     layerChoreographers.frameRate = fps;
-    ATRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+    SFTRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
     ALOGV("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
 
     auto it = layerChoreographers.connections.begin();
@@ -1075,13 +1101,13 @@
 
 void Scheduler::updateAttachedChoreographers(
         const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     updateAttachedChoreographersInternal(layerHierarchy, displayRefreshRate, 0);
 }
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
 
@@ -1118,66 +1144,68 @@
         for (auto& [id, choice] : modeChoices) {
             modeRequests.emplace_back(
                     display::DisplayModeRequest{.mode = std::move(choice.mode),
-                                                .emitEvent = !choice.consideredSignals.idle});
+                                                .emitEvent = choice.consideredSignals
+                                                                     .shouldEmitEvent()});
         }
 
-        frameRateOverridesChanged = updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
-
+        if (!FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+            frameRateOverridesChanged =
+                    updateFrameRateOverridesLocked(consideredSignals, modeOpt->fps);
+        }
         if (mPolicy.modeOpt != modeOpt) {
             mPolicy.modeOpt = modeOpt;
             refreshRateChanged = true;
-        } else {
-            // We don't need to change the display mode, but we might need to send an event
-            // about a mode change, since it was suppressed if previously considered idle.
-            if (!consideredSignals.idle) {
-                dispatchCachedReportedMode();
-            }
+        } else if (consideredSignals.shouldEmitEvent()) {
+            // The mode did not change, but we may need to emit if DisplayModeRequest::emitEvent was
+            // previously false.
+            emitModeChangeIfNeeded();
         }
     }
     if (refreshRateChanged) {
         mSchedulerCallback.requestDisplayModes(std::move(modeRequests));
     }
+
+    if (FlagManager::getInstance().vrr_bugfix_dropped_frame()) {
+        std::scoped_lock lock(mPolicyLock);
+        frameRateOverridesChanged =
+                updateFrameRateOverridesLocked(consideredSignals, mPolicy.modeOpt->fps);
+    }
     if (frameRateOverridesChanged) {
-        mSchedulerCallback.triggerOnFrameRateOverridesChanged();
+        onFrameRateOverridesChanged();
     }
     return consideredSignals;
 }
 
 auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
-    ATRACE_CALL();
-
-    using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
-    ui::PhysicalDisplayVector<RankedRefreshRates> perDisplayRanking;
-    const auto globalSignals = makeGlobalSignals();
-    Fps pacesetterFps;
-
-    for (const auto& [id, display] : mDisplays) {
-        auto rankedFrameRates =
-                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements,
-                                                         globalSignals);
-        if (id == *mPacesetterDisplayId) {
-            pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
-        }
-        perDisplayRanking.push_back(std::move(rankedFrameRates));
-    }
+    SFTRACE_CALL();
 
     DisplayModeChoiceMap modeChoices;
-    using fps_approx_ops::operator==;
+    const auto globalSignals = makeGlobalSignals();
 
-    for (auto& [rankings, signals] : perDisplayRanking) {
-        const auto chosenFrameRateMode =
-                ftl::find_if(rankings,
-                             [&](const auto& ranking) {
-                                 return ranking.frameRateMode.fps == pacesetterFps;
-                             })
-                        .transform([](const auto& scoredFrameRate) {
-                            return scoredFrameRate.get().frameRateMode;
-                        })
-                        .value_or(rankings.front().frameRateMode);
+    const Fps pacesetterFps = [&]() REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext) {
+        auto rankedFrameRates =
+                pacesetterSelectorPtrLocked()->getRankedFrameRates(mPolicy.contentRequirements,
+                                                                   globalSignals);
 
-        modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
-                                DisplayModeChoice{chosenFrameRateMode, signals});
+        const Fps pacesetterFps = rankedFrameRates.ranking.front().frameRateMode.fps;
+
+        modeChoices.try_emplace(*mPacesetterDisplayId,
+                                DisplayModeChoice::from(std::move(rankedFrameRates)));
+        return pacesetterFps;
+    }();
+
+    // Choose a mode for powered-on follower displays.
+    for (const auto& [id, display] : mDisplays) {
+        if (id == *mPacesetterDisplayId) continue;
+        if (display.powerMode != hal::PowerMode::ON) continue;
+
+        auto rankedFrameRates =
+                display.selectorPtr->getRankedFrameRates(mPolicy.contentRequirements, globalSignals,
+                                                         pacesetterFps);
+
+        modeChoices.try_emplace(id, DisplayModeChoice::from(std::move(rankedFrameRates)));
     }
+
     return modeChoices;
 }
 
@@ -1243,6 +1271,8 @@
     } else {
         mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
     }
+
+    onFrameRateOverridesChanged();
 }
 
 void Scheduler::setGameDefaultFrameRateForUid(FrameRateOverride frameRateOverride) {
@@ -1261,6 +1291,7 @@
     }
 
     mFrameRateOverrideMappings.setPreferredRefreshRateForUid(frameRateOverride);
+    onFrameRateOverridesChanged();
 }
 
 void Scheduler::updateSmallAreaDetection(
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 09f75fd..c88b563 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -92,8 +92,8 @@
 
     void startTimers();
 
-    // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}.
-    void setPacesetterDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext)
+    // TODO: b/241285191 - Remove this API by promoting pacesetter in onScreen{Acquired,Released}.
+    void setPacesetterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
 
     using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
@@ -101,9 +101,16 @@
     using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
     using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
 
-    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr) REQUIRES(kMainThreadContext)
+    // After registration/unregistration, `activeDisplayId` is promoted to pacesetter. Note that the
+    // active display is never unregistered, since hotplug disconnect never happens for activatable
+    // displays, i.e. a foldable's internal displays or otherwise the (internal or external) primary
+    // display.
+    // TODO: b/255635821 - Remove active display parameters.
+    void registerDisplay(PhysicalDisplayId, RefreshRateSelectorPtr,
+                         PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
             EXCLUDES(mDisplayLock);
-    void unregisterDisplay(PhysicalDisplayId) REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void unregisterDisplay(PhysicalDisplayId, PhysicalDisplayId activeDisplayId)
+            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     void run();
 
@@ -138,22 +145,16 @@
             Cycle, EventRegistrationFlags eventRegistration = {},
             const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
 
-    const sp<EventThreadConnection>& getEventConnection(Cycle cycle) const {
-        return cycle == Cycle::Render ? mRenderEventConnection : mLastCompositeEventConnection;
-    }
-
     enum class Hotplug { Connected, Disconnected };
     void dispatchHotplug(PhysicalDisplayId, Hotplug);
 
     void dispatchHotplugError(int32_t errorCode);
 
-    void onPrimaryDisplayModeChanged(Cycle, const FrameRateMode&) EXCLUDES(mPolicyLock);
-    void onNonPrimaryDisplayModeChanged(Cycle, const FrameRateMode&);
+    // Returns true if the PhysicalDisplayId is the pacesetter.
+    bool onDisplayModeChanged(PhysicalDisplayId, const FrameRateMode&) EXCLUDES(mPolicyLock);
 
     void enableSyntheticVsync(bool = true) REQUIRES(kMainThreadContext);
 
-    void onFrameRateOverridesChanged(Cycle, PhysicalDisplayId);
-
     void onHdcpLevelsChanged(Cycle, PhysicalDisplayId, int32_t, int32_t);
 
     // Modifies work duration in the event thread.
@@ -182,13 +183,12 @@
         }
     }
 
-    void updatePhaseConfiguration(Fps);
-    void resetPhaseConfiguration(Fps) REQUIRES(kMainThreadContext);
+    void updatePhaseConfiguration(PhysicalDisplayId, Fps);
 
     const VsyncConfiguration& getVsyncConfiguration() const { return *mVsyncConfiguration; }
 
     // Sets the render rate for the scheduler to run at.
-    void setRenderRate(PhysicalDisplayId, Fps);
+    void setRenderRate(PhysicalDisplayId, Fps, bool applyImmediately);
 
     void enableHardwareVsync(PhysicalDisplayId) REQUIRES(kMainThreadContext);
     void disableHardwareVsync(PhysicalDisplayId, bool disallow) REQUIRES(kMainThreadContext);
@@ -215,7 +215,7 @@
             REQUIRES(kMainThreadContext);
 
     // Layers are registered on creation, and unregistered when the weak reference expires.
-    void registerLayer(Layer*);
+    void registerLayer(Layer*, FrameRateCompatibility);
     void recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
                             nsecs_t now, LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
     void setModeChangePending(bool pending);
@@ -319,7 +319,7 @@
         return mLayerHistory.getLayerFramerate(now, id);
     }
 
-    bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
+    void updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
 
     // Returns true if the small dirty detection is enabled for the appId.
     bool supportSmallDirtyDetection(int32_t appId) {
@@ -346,7 +346,9 @@
     // Used to skip event dispatch before EventThread creation during boot.
     // TODO: b/241285191 - Reorder Scheduler initialization to avoid this.
     bool hasEventThreads() const {
-        return CC_LIKELY(mRenderEventThread && mLastCompositeEventThread);
+        return CC_LIKELY(
+                mRenderEventThread &&
+                (FlagManager::getInstance().deprecate_vsync_sf() || mLastCompositeEventThread));
     }
 
     EventThread& eventThreadFor(Cycle cycle) const {
@@ -368,9 +370,16 @@
     void resyncAllToHardwareVsync(bool allowToEnable) EXCLUDES(mDisplayLock);
     void setVsyncConfig(const VsyncConfig&, Period vsyncPeriod);
 
-    // Chooses a pacesetter among the registered displays, unless `pacesetterIdOpt` is specified.
-    // The new `mPacesetterDisplayId` is never `std::nullopt`.
-    void promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
+    // TODO: b/241286431 - Remove this option, which assumes that the pacesetter does not change
+    // when a (secondary) display is registered or unregistered. In the short term, this avoids
+    // a deadlock where the main thread joins with the timer thread as the timer thread waits to
+    // lock a mutex held by the main thread.
+    struct PromotionParams {
+        // Whether to stop and start the idle timer. Ignored unless connected_display flag is set.
+        bool toggleIdleTimer;
+    };
+
+    void promotePacesetterDisplay(PhysicalDisplayId pacesetterId, PromotionParams)
             REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
 
     // Changes to the displays (e.g. registering and unregistering) must be made
@@ -379,17 +388,20 @@
     // MessageQueue and EventThread need to use the new pacesetter's
     // VsyncSchedule, and this must happen while mDisplayLock is *not* locked,
     // or else we may deadlock with EventThread.
-    std::shared_ptr<VsyncSchedule> promotePacesetterDisplayLocked(
-            std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
+    std::shared_ptr<VsyncSchedule> promotePacesetterDisplayLocked(PhysicalDisplayId pacesetterId,
+                                                                  PromotionParams)
             REQUIRES(kMainThreadContext, mDisplayLock);
     void applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule>) EXCLUDES(mDisplayLock);
 
-    // Blocks until the pacesetter's idle timer thread exits. `mDisplayLock` must not be locked by
-    // the caller on the main thread to avoid deadlock, since the timer thread locks it before exit.
-    void demotePacesetterDisplay() REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock, mPolicyLock);
+    // If toggleIdleTimer is true, the calling thread blocks until the pacesetter's idle timer
+    // thread exits, in which case mDisplayLock must not be locked by the caller to avoid deadlock,
+    // since the timer thread locks it before exit.
+    void demotePacesetterDisplay(PromotionParams) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock, mPolicyLock);
 
-    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr)
-            REQUIRES(kMainThreadContext) EXCLUDES(mDisplayLock);
+    void registerDisplayInternal(PhysicalDisplayId, RefreshRateSelectorPtr, VsyncSchedulePtr,
+                                 PhysicalDisplayId activeDisplayId) REQUIRES(kMainThreadContext)
+            EXCLUDES(mDisplayLock);
 
     struct Policy;
 
@@ -402,6 +414,11 @@
         DisplayModeChoice(FrameRateMode mode, GlobalSignals consideredSignals)
               : mode(std::move(mode)), consideredSignals(consideredSignals) {}
 
+        static DisplayModeChoice from(RefreshRateSelector::RankedFrameRates rankedFrameRates) {
+            return {rankedFrameRates.ranking.front().frameRateMode,
+                    rankedFrameRates.consideredSignals};
+        }
+
         FrameRateMode mode;
         GlobalSignals consideredSignals;
 
@@ -426,6 +443,9 @@
 
     bool updateFrameRateOverridesLocked(GlobalSignals, Fps displayRefreshRate)
             REQUIRES(mPolicyLock);
+
+    void onFrameRateOverridesChanged();
+
     void updateAttachedChoreographers(const surfaceflinger::frontend::LayerHierarchy&,
                                       Fps displayRefreshRate);
     int updateAttachedChoreographersInternal(const surfaceflinger::frontend::LayerHierarchy&,
@@ -433,19 +453,17 @@
     void updateAttachedChoreographersFrameRate(const surfaceflinger::frontend::RequestedLayerState&,
                                                Fps fps) EXCLUDES(mChoreographerLock);
 
-    void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
+    void emitModeChangeIfNeeded() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
 
     // IEventThreadCallback overrides
     bool throttleVsync(TimePoint, uid_t) override;
+    // Get frame interval
     Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
     void resync() override EXCLUDES(mDisplayLock);
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     std::unique_ptr<EventThread> mRenderEventThread;
-    sp<EventThreadConnection> mRenderEventConnection;
-
     std::unique_ptr<EventThread> mLastCompositeEventThread;
-    sp<EventThreadConnection> mLastCompositeEventConnection;
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
@@ -558,13 +576,8 @@
         // Chosen display mode.
         ftl::Optional<FrameRateMode> modeOpt;
 
-        struct ModeChangedParams {
-            Cycle cycle;
-            FrameRateMode mode;
-        };
-
-        // Parameters for latest dispatch of mode change event.
-        std::optional<ModeChangedParams> cachedModeChangedParams;
+        // Display mode of latest emitted event.
+        std::optional<FrameRateMode> emittedModeOpt;
     } mPolicy GUARDED_BY(mPolicyLock);
 
     std::mutex mChoreographerLock;
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 0c43ffb..8993c38 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatch.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatch.h
@@ -93,6 +93,8 @@
      *                 readyDuration will typically be 0.
      * @lastVsync: The targeted display time. This will be snapped to the closest
      *                 predicted vsync time after lastVsync.
+     * @committedVsyncOpt: The display time that is committed to the callback as the
+     *                 target vsync time.
      *
      * callback will be dispatched at 'workDuration + readyDuration' nanoseconds before a vsync
      * event.
@@ -101,10 +103,11 @@
         nsecs_t workDuration = 0;
         nsecs_t readyDuration = 0;
         nsecs_t lastVsync = 0;
+        std::optional<nsecs_t> committedVsyncOpt;
 
         bool operator==(const ScheduleTiming& other) const {
             return workDuration == other.workDuration && readyDuration == other.readyDuration &&
-                    lastVsync == other.lastVsync;
+                    lastVsync == other.lastVsync && committedVsyncOpt == other.committedVsyncOpt;
         }
 
         bool operator!=(const ScheduleTiming& other) const { return !(*this == other); }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 6d6b70d..1925f11 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -19,8 +19,8 @@
 #include <vector>
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log_main.h>
 
 #include <scheduler/TimeKeeper.h>
@@ -45,14 +45,14 @@
 }
 
 void traceEntry(const VSyncDispatchTimerQueueEntry& entry, nsecs_t now) {
-    if (!ATRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) {
+    if (!SFTRACE_ENABLED() || !entry.wakeupTime().has_value() || !entry.targetVsync().has_value()) {
         return;
     }
 
     ftl::Concat trace(ftl::truncated<5>(entry.name()), " alarm in ",
                       ns2us(*entry.wakeupTime() - now), "us; VSYNC in ",
                       ns2us(*entry.targetVsync() - now), "us");
-    ATRACE_FORMAT_INSTANT(trace.c_str());
+    SFTRACE_FORMAT_INSTANT(trace.c_str());
 }
 
 } // namespace
@@ -98,20 +98,21 @@
 
 ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTiming timing,
                                                       VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::schedule");
     auto nextVsyncTime =
             tracker.nextAnticipatedVSyncTimeFrom(std::max(timing.lastVsync,
                                                           now + timing.workDuration +
                                                                   timing.readyDuration),
-                                                 timing.lastVsync);
+                                                 timing.committedVsyncOpt.value_or(
+                                                         timing.lastVsync));
     auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 
     bool const wouldSkipAVsyncTarget =
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
     bool const wouldSkipAWakeup =
             mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
-    ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                          wouldSkipAVsyncTarget, wouldSkipAWakeup);
+    SFTRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                           wouldSkipAVsyncTarget, wouldSkipAWakeup);
     if (FlagManager::getInstance().dont_skip_on_early_ro()) {
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             nextVsyncTime = mArmedInfo->mActualVsyncTime;
@@ -154,13 +155,13 @@
     bool const nextVsyncTooClose = mLastDispatchTime &&
             (nextVsyncTime - *mLastDispatchTime + mMinVsyncDistance) <= currentPeriod;
     if (alreadyDispatchedForVsync) {
-        ATRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
+        SFTRACE_FORMAT_INSTANT("alreadyDispatchedForVsync");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance,
                                                     *mLastDispatchTime);
     }
 
     if (nextVsyncTooClose) {
-        ATRACE_FORMAT_INSTANT("nextVsyncTooClose");
+        SFTRACE_FORMAT_INSTANT("nextVsyncTooClose");
         return tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + currentPeriod,
                                                     *mLastDispatchTime + currentPeriod);
     }
@@ -172,7 +173,7 @@
                                                 VSyncDispatch::ScheduleTiming timing,
                                                 std::optional<ArmingInfo> armedInfo) const
         -> ArmingInfo {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::getArmedInfo");
     const auto earliestReadyBy = now + timing.workDuration + timing.readyDuration;
     const auto earliestVsync = std::max(earliestReadyBy, timing.lastVsync);
 
@@ -188,8 +189,8 @@
                 armedInfo && (nextVsyncTime > (armedInfo->mActualVsyncTime + mMinVsyncDistance));
         bool const wouldSkipAWakeup =
                 armedInfo && (nextWakeupTime > (armedInfo->mActualWakeupTime + mMinVsyncDistance));
-        ATRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
-                              wouldSkipAVsyncTarget, wouldSkipAWakeup);
+        SFTRACE_FORMAT_INSTANT("%s: wouldSkipAVsyncTarget=%d wouldSkipAWakeup=%d", mName.c_str(),
+                               wouldSkipAVsyncTarget, wouldSkipAWakeup);
         if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
             return *armedInfo;
         }
@@ -199,7 +200,7 @@
 }
 
 void VSyncDispatchTimerQueueEntry::update(VSyncTracker& tracker, nsecs_t now) {
-    ATRACE_NAME("VSyncDispatchTimerQueueEntry::update");
+    SFTRACE_NAME("VSyncDispatchTimerQueueEntry::update");
     if (!mArmedInfo && !mWorkloadUpdateInfo) {
         return;
     }
@@ -208,9 +209,12 @@
         const auto workDelta = mWorkloadUpdateInfo->workDuration - mScheduleTiming.workDuration;
         const auto readyDelta = mWorkloadUpdateInfo->readyDuration - mScheduleTiming.readyDuration;
         const auto lastVsyncDelta = mWorkloadUpdateInfo->lastVsync - mScheduleTiming.lastVsync;
-        ATRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
-                              " lastVsyncDelta=%" PRId64,
-                              workDelta, readyDelta, lastVsyncDelta);
+        const auto lastCommittedVsyncDelta =
+                mWorkloadUpdateInfo->committedVsyncOpt.value_or(mWorkloadUpdateInfo->lastVsync) -
+                mScheduleTiming.committedVsyncOpt.value_or(mScheduleTiming.lastVsync);
+        SFTRACE_FORMAT_INSTANT("Workload updated workDelta=%" PRId64 " readyDelta=%" PRId64
+                               " lastVsyncDelta=%" PRId64 " committedVsyncDelta=%" PRId64,
+                               workDelta, readyDelta, lastVsyncDelta, lastCommittedVsyncDelta);
         mScheduleTiming = *mWorkloadUpdateInfo;
         mWorkloadUpdateInfo.reset();
     }
@@ -261,10 +265,14 @@
     StringAppendF(&result, "\t\t%s: %s %s\n", mName.c_str(),
                   mRunning ? "(in callback function)" : "", armedInfo.c_str());
     StringAppendF(&result,
-                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms lastVsync: %.2fms relative "
-                  "to now\n",
+                  "\t\t\tworkDuration: %.2fms readyDuration: %.2fms "
+                  "lastVsync: %.2fms relative to now "
+                  "committedVsync: %.2fms relative to now\n",
                   mScheduleTiming.workDuration / 1e6f, mScheduleTiming.readyDuration / 1e6f,
-                  (mScheduleTiming.lastVsync - systemTime()) / 1e6f);
+                  (mScheduleTiming.lastVsync - systemTime()) / 1e6f,
+                  (mScheduleTiming.committedVsyncOpt.value_or(mScheduleTiming.lastVsync) -
+                   systemTime()) /
+                          1e6f);
 
     if (mLastDispatchTime) {
         StringAppendF(&result, "\t\t\tmLastDispatchTime: %.2fms ago\n",
@@ -310,7 +318,7 @@
 
 void VSyncDispatchTimerQueue::rearmTimerSkippingUpdateFor(
         nsecs_t now, CallbackMap::const_iterator skipUpdateIt) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::optional<nsecs_t> min;
     std::optional<nsecs_t> targetVsync;
     std::optional<std::string_view> nextWakeupName;
@@ -337,13 +345,13 @@
     if (min && min < mIntendedWakeupTime) {
         setTimer(*min, now);
     } else {
-        ATRACE_NAME("cancel timer");
+        SFTRACE_NAME("cancel timer");
         cancelTimer();
     }
 }
 
 void VSyncDispatchTimerQueue::timerCallback() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     struct Invocation {
         std::shared_ptr<VSyncDispatchTimerQueueEntry> callback;
         nsecs_t vsyncTimestamp;
@@ -383,7 +391,7 @@
 
     for (auto const& invocation : invocations) {
         ftl::Concat trace(ftl::truncated<5>(invocation.callback->name()));
-        ATRACE_FORMAT("%s: %s", __func__, trace.c_str());
+        SFTRACE_FORMAT("%s: %s", __func__, trace.c_str());
         invocation.callback->callback(invocation.vsyncTimestamp, invocation.wakeupTimestamp,
                                       invocation.deadlineTimestamp);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 2f9dfea..6e36f02 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -30,10 +30,10 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <common/FlagManager.h>
+#include <common/trace.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
 #include "RefreshRateSelector.h"
@@ -46,24 +46,11 @@
 static auto constexpr kMaxPercent = 100u;
 
 namespace {
-nsecs_t getVsyncFixup(VSyncPredictor::Model model, Period minFramePeriod, nsecs_t vsyncTime,
-                      std::optional<nsecs_t> lastVsyncOpt) {
-    const auto threshold = model.slope / 2;
-
-    if (FlagManager::getInstance().vrr_config() && lastVsyncOpt) {
-        const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
-        if (vsyncDiff >= threshold && vsyncDiff <= minFramePeriod.ns() - threshold) {
-            const auto vsyncFixup = *lastVsyncOpt + minFramePeriod.ns() - vsyncTime;
-            ATRACE_FORMAT_INSTANT("minFramePeriod violation. next in %.2f which is %.2f from prev. "
-                                  "adjust by %.2f",
-                                  static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
-                                  static_cast<float>(vsyncTime - *lastVsyncOpt) / 1e6f,
-                                  static_cast<float>(vsyncFixup) / 1e6f);
-            return vsyncFixup;
-        }
-    }
-
-    return 0;
+int numVsyncsPerFrame(const ftl::NonNull<DisplayModePtr>& displayModePtr) {
+    const auto idealPeakRefreshPeriod = displayModePtr->getPeakFps().getPeriodNsecs();
+    const auto idealRefreshPeriod = displayModePtr->getVsyncRate().getPeriodNsecs();
+    return static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
+                                       static_cast<float>(idealRefreshPeriod)));
 }
 } // namespace
 
@@ -78,7 +65,8 @@
         kHistorySize(historySize),
         kMinimumSamplesForPrediction(minimumSamplesForPrediction),
         kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
-        mDisplayModePtr(modePtr) {
+        mDisplayModePtr(modePtr),
+        mNumVsyncsForFrame(numVsyncsPerFrame(mDisplayModePtr)) {
     resetModel();
 }
 
@@ -89,7 +77,7 @@
 }
 
 inline void VSyncPredictor::traceInt64(const char* name, int64_t value) const {
-    ATRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
+    SFTRACE_INT64(ftl::Concat(ftl::truncated<14>(name), " ", mId.value).c_str(), value);
 }
 
 inline size_t VSyncPredictor::next(size_t i) const {
@@ -101,7 +89,9 @@
 }
 
 bool VSyncPredictor::validate(nsecs_t timestamp) const {
+    SFTRACE_CALL();
     if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
+        SFTRACE_INSTANT("timestamp valid (first)");
         return true;
     }
 
@@ -110,7 +100,11 @@
             (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent &&
         percent <= (kMaxPercent - kOutlierTolerancePercent)) {
-        ATRACE_FORMAT_INSTANT("timestamp is not aligned with model");
+        SFTRACE_FORMAT_INSTANT("timestamp not aligned with model. aValidTimestamp %.2fms ago"
+                               ", timestamp %.2fms ago, idealPeriod=%.2 percent=%d",
+                               (mClock->now() - aValidTimestamp) / 1e6f,
+                               (mClock->now() - timestamp) / 1e6f,
+                               idealPeriod() / 1e6f, percent);
         return false;
     }
 
@@ -121,7 +115,7 @@
     const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
     if (distancePercent < kOutlierTolerancePercent) {
         // duplicate timestamp
-        ATRACE_FORMAT_INSTANT("duplicate timestamp");
+        SFTRACE_FORMAT_INSTANT("duplicate timestamp");
         return false;
     }
     return true;
@@ -142,15 +136,12 @@
 }
 
 Period VSyncPredictor::minFramePeriodLocked() const {
-    const auto idealPeakRefreshPeriod = mDisplayModePtr->getPeakFps().getPeriodNsecs();
-    const auto numPeriods = static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
-                                                        static_cast<float>(idealPeriod())));
     const auto slope = mRateMap.find(idealPeriod())->second.slope;
-    return Period::fromNs(slope * numPeriods);
+    return Period::fromNs(slope * mNumVsyncsForFrame);
 }
 
 bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard lock(mMutex);
 
@@ -163,15 +154,18 @@
             // Add the timestamp to mTimestamps before clearing it so we could
             // update mKnownTimestamp based on the new timestamp.
             mTimestamps.push_back(timestamp);
-            clearTimestamps();
+
+            // Do not clear timelines as we don't want to break the phase while
+            // we are still learning.
+            clearTimestamps(/* clearTimelines */ false);
         } else if (!mTimestamps.empty()) {
             mKnownTimestamp =
                     std::max(timestamp, *std::max_element(mTimestamps.begin(), mTimestamps.end()));
         } else {
             mKnownTimestamp = timestamp;
         }
-        ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
-                              (mClock->now() - *mKnownTimestamp) / 1e6f);
+        SFTRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         return false;
     }
 
@@ -250,7 +244,7 @@
 
     if (CC_UNLIKELY(bottom == 0)) {
         it->second = {idealPeriod(), 0};
-        clearTimestamps();
+        clearTimestamps(/* clearTimelines */ true);
         return false;
     }
 
@@ -260,7 +254,7 @@
     auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
     if (percent >= kOutlierTolerancePercent) {
         it->second = {idealPeriod(), 0};
-        clearTimestamps();
+        clearTimestamps(/* clearTimelines */ true);
         return false;
     }
 
@@ -312,18 +306,31 @@
 
 nsecs_t VSyncPredictor::nextAnticipatedVSyncTimeFrom(nsecs_t timePoint,
                                                      std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard lock(mMutex);
 
     const auto now = TimePoint::fromNs(mClock->now());
     purgeTimelines(now);
 
+    if (lastVsyncOpt && *lastVsyncOpt > timePoint) {
+        timePoint = *lastVsyncOpt;
+    }
+
+    const auto model = getVSyncPredictionModelLocked();
+    const auto threshold = model.slope / 2;
+    std::optional<Period> minFramePeriodOpt;
+
+    if (mNumVsyncsForFrame > 1) {
+        minFramePeriodOpt = minFramePeriodLocked();
+    }
+
     std::optional<TimePoint> vsyncOpt;
     for (auto& timeline : mTimelines) {
-        vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(getVSyncPredictionModelLocked(),
-                                                         minFramePeriodLocked(),
+        vsyncOpt = timeline.nextAnticipatedVSyncTimeFrom(model, minFramePeriodOpt,
                                                          snapToVsync(timePoint), mMissedVsync,
-                                                         lastVsyncOpt);
+                                                         lastVsyncOpt ? snapToVsync(*lastVsyncOpt -
+                                                                                    threshold)
+                                                                      : lastVsyncOpt);
         if (vsyncOpt) {
             break;
         }
@@ -332,8 +339,8 @@
 
     if (*vsyncOpt > mLastCommittedVsync) {
         mLastCommittedVsync = *vsyncOpt;
-        ATRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
-                              float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
+        SFTRACE_FORMAT_INSTANT("mLastCommittedVsync in %.2fms",
+                               float(mLastCommittedVsync.ns() - mClock->now()) / 1e6f);
     }
 
     return vsyncOpt->ns();
@@ -362,7 +369,11 @@
     purgeTimelines(now);
 
     for (auto& timeline : mTimelines) {
-        if (timeline.validUntil() && timeline.validUntil()->ns() > vsync) {
+        const bool isVsyncValid = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? timeline.isWithin(TimePoint::fromNs(vsync)) ==
+                        VsyncTimeline::VsyncOnTimeline::Unique
+                : timeline.validUntil() && timeline.validUntil()->ns() > vsync;
+        if (isVsyncValid) {
             return timeline.isVSyncInPhase(model, vsync, frameRate);
         }
     }
@@ -371,20 +382,50 @@
     return mTimelines.back().isVSyncInPhase(model, vsync, frameRate);
 }
 
-void VSyncPredictor::setRenderRate(Fps renderRate) {
-    ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
+void VSyncPredictor::setRenderRate(Fps renderRate, bool applyImmediately) {
+    SFTRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
     ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
     std::lock_guard lock(mMutex);
+    const auto prevRenderRate = mRenderRateOpt;
     mRenderRateOpt = renderRate;
-    mTimelines.back().freeze(TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
-    mTimelines.emplace_back(mIdealPeriod, renderRate);
+    const auto renderPeriodDelta =
+            prevRenderRate ? prevRenderRate->getPeriodNsecs() - renderRate.getPeriodNsecs() : 0;
+    if (applyImmediately) {
+        SFTRACE_FORMAT_INSTANT("applyImmediately");
+        while (mTimelines.size() > 1) {
+            mTimelines.pop_front();
+        }
+
+        mTimelines.front().setRenderRate(renderRate);
+        return;
+    }
+
+    const bool newRenderRateIsHigher = renderPeriodDelta > renderRate.getPeriodNsecs() &&
+            mLastCommittedVsync.ns() - mClock->now() > 2 * renderRate.getPeriodNsecs();
+    if (newRenderRateIsHigher) {
+        SFTRACE_FORMAT_INSTANT("newRenderRateIsHigher");
+        mTimelines.clear();
+        mLastCommittedVsync = TimePoint::fromNs(0);
+
+    } else {
+        if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+            // We need to freeze the timeline at the committed vsync, and
+            // then use with threshold adjustments when required to avoid
+            // marginal errors when checking the vsync on the timeline.
+            mTimelines.back().freeze(mLastCommittedVsync);
+        } else {
+            mTimelines.back().freeze(
+                    TimePoint::fromNs(mLastCommittedVsync.ns() + mIdealPeriod.ns() / 2));
+        }
+    }
+    mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, renderRate);
     purgeTimelines(TimePoint::fromNs(mClock->now()));
 }
 
 void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
     LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
                         "mode does not belong to the display");
-    ATRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
+    SFTRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
     const auto timeout = modePtr->getVrrConfig()
             ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
             : std::nullopt;
@@ -393,7 +434,11 @@
           timeout ? std::to_string(timeout->timeoutNs).c_str() : "N/A");
     std::lock_guard lock(mMutex);
 
+    // do not clear the timelines on VRR displays if we didn't change the mode
+    const bool isVrr = modePtr->getVrrConfig().has_value();
+    const bool clearTimelines = !isVrr || mDisplayModePtr->getId() != modePtr->getId();
     mDisplayModePtr = modePtr;
+    mNumVsyncsForFrame = numVsyncsPerFrame(mDisplayModePtr);
     traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
 
     static constexpr size_t kSizeLimit = 30;
@@ -405,28 +450,37 @@
         mRateMap[idealPeriod()] = {idealPeriod(), 0};
     }
 
-    clearTimestamps();
+    if (clearTimelines) {
+      mTimelines.clear();
+    }
+    clearTimestamps(clearTimelines);
 }
 
 Duration VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
                                                       TimePoint lastConfirmedPresentTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
+
+    if (mNumVsyncsForFrame <= 1) {
+        return 0ns;
+    }
+
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
-    const auto minFramePeriod = minFramePeriodLocked().ns();
+    const auto minFramePeriod = minFramePeriodLocked();
 
     auto prev = lastConfirmedPresentTime.ns();
     for (auto& current : mPastExpectedPresentTimes) {
         if (CC_UNLIKELY(mTraceOn)) {
-            ATRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
-                                  static_cast<float>(current.ns() - lastConfirmedPresentTime.ns()) /
-                                          1e6f);
+            SFTRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
+                                   static_cast<float>(current.ns() -
+                                                      lastConfirmedPresentTime.ns()) /
+                                           1e6f);
         }
 
-        const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod;
+        const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod.ns();
         if (minPeriodViolation) {
-            ATRACE_NAME("minPeriodViolation");
-            current = TimePoint::fromNs(prev + minFramePeriod);
+            SFTRACE_NAME("minPeriodViolation");
+            current = TimePoint::fromNs(prev + minFramePeriod.ns());
             prev = current.ns();
         } else {
             break;
@@ -437,7 +491,7 @@
         const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
         if (phase > 0ns) {
             for (auto& timeline : mTimelines) {
-                timeline.shiftVsyncSequence(phase);
+                timeline.shiftVsyncSequence(phase, minFramePeriod);
             }
             mPastExpectedPresentTimes.clear();
             return phase;
@@ -447,18 +501,18 @@
     return 0ns;
 }
 
-void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
-                                  TimePoint lastConfirmedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameBegin");
+void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime, FrameTime lastSignaledFrameTime) {
+    SFTRACE_NAME("VSyncPredictor::onFrameBegin");
     std::lock_guard lock(mMutex);
 
     if (!mDisplayModePtr->getVrrConfig()) return;
 
+    const auto [lastConfirmedPresentTime, lastConfirmedExpectedPresentTime] = lastSignaledFrameTime;
     if (CC_UNLIKELY(mTraceOn)) {
-        ATRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
-                              static_cast<float>(expectedPresentTime.ns() -
-                                                 lastConfirmedPresentTime.ns()) /
-                                      1e6f);
+        SFTRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
+                               static_cast<float>(expectedPresentTime.ns() -
+                                                  lastConfirmedPresentTime.ns()) /
+                                       1e6f);
     }
     const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
     const auto threshold = currentPeriod / 2;
@@ -469,9 +523,9 @@
         const bool frontIsBeforeConfirmed = front < lastConfirmedPresentTime.ns() + threshold;
         if (frontIsBeforeConfirmed) {
             if (CC_UNLIKELY(mTraceOn)) {
-                ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
-                                      static_cast<float>(lastConfirmedPresentTime.ns() - front) /
-                                              1e6f);
+                SFTRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+                                       static_cast<float>(lastConfirmedPresentTime.ns() - front) /
+                                               1e6f);
             }
             mPastExpectedPresentTimes.pop_front();
         } else {
@@ -479,6 +533,11 @@
         }
     }
 
+    if (lastConfirmedExpectedPresentTime.ns() - lastConfirmedPresentTime.ns() > threshold) {
+        SFTRACE_FORMAT_INSTANT("lastFramePresentedEarly");
+        return;
+    }
+
     const auto phase = ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
     if (phase > 0ns) {
         mMissedVsync = {expectedPresentTime, minFramePeriodLocked()};
@@ -486,7 +545,7 @@
 }
 
 void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
-    ATRACE_NAME("VSyncPredictor::onFrameMissed");
+    SFTRACE_NAME("VSyncPredictor::onFrameMissed");
 
     std::lock_guard lock(mMutex);
     if (!mDisplayModePtr->getVrrConfig()) return;
@@ -511,25 +570,42 @@
     return mRateMap.find(idealPeriod())->second;
 }
 
-void VSyncPredictor::clearTimestamps() {
-    ATRACE_CALL();
+void VSyncPredictor::clearTimestamps(bool clearTimelines) {
+    SFTRACE_FORMAT("%s: clearTimelines=%d", __func__, clearTimelines);
 
     if (!mTimestamps.empty()) {
         auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
         if (mKnownTimestamp) {
             mKnownTimestamp = std::max(*mKnownTimestamp, maxRb);
+            SFTRACE_FORMAT_INSTANT("mKnownTimestamp was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         } else {
             mKnownTimestamp = maxRb;
+            SFTRACE_FORMAT_INSTANT("mKnownTimestamp (maxRb) was %.2fms ago",
+                               (mClock->now() - *mKnownTimestamp) / 1e6f);
         }
 
         mTimestamps.clear();
         mLastTimestampIndex = 0;
     }
 
-    mTimelines.clear();
-    mLastCommittedVsync = TimePoint::fromNs(0);
     mIdealPeriod = Period::fromNs(idealPeriod());
-    mTimelines.emplace_back(mIdealPeriod, mRenderRateOpt);
+    if (mTimelines.empty()) {
+        mLastCommittedVsync = TimePoint::fromNs(0);
+        mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+    } else if (clearTimelines) {
+        while (mTimelines.size() > 1) {
+            mTimelines.pop_front();
+        }
+        mTimelines.front().setRenderRate(mRenderRateOpt);
+        // set mLastCommittedVsync to a valid vsync but don't commit too much in the future
+        const auto vsyncOpt = mTimelines.front().nextAnticipatedVSyncTimeFrom(
+            getVSyncPredictionModelLocked(),
+            /* minFramePeriodOpt */ std::nullopt,
+            snapToVsync(mClock->now()), MissedVsync{},
+            /* lastVsyncOpt */ std::nullopt);
+        mLastCommittedVsync = *vsyncOpt;
+    }
 }
 
 bool VSyncPredictor::needsMoreSamples() const {
@@ -538,9 +614,10 @@
 }
 
 void VSyncPredictor::resetModel() {
+    SFTRACE_CALL();
     std::lock_guard lock(mMutex);
     mRateMap[idealPeriod()] = {idealPeriod(), 0};
-    clearTimestamps();
+    clearTimestamps(/* clearTimelines */ true);
 }
 
 void VSyncPredictor::dump(std::string& result) const {
@@ -557,9 +634,23 @@
 }
 
 void VSyncPredictor::purgeTimelines(android::TimePoint now) {
+    const auto kEnoughFramesToBreakPhase = 5;
+    if (mRenderRateOpt &&
+        mLastCommittedVsync.ns() + mRenderRateOpt->getPeriodNsecs() * kEnoughFramesToBreakPhase <
+                mClock->now()) {
+        SFTRACE_FORMAT_INSTANT("kEnoughFramesToBreakPhase");
+        mTimelines.clear();
+        mLastCommittedVsync = TimePoint::fromNs(0);
+        mTimelines.emplace_back(mLastCommittedVsync, mIdealPeriod, mRenderRateOpt);
+        return;
+    }
+
     while (mTimelines.size() > 1) {
         const auto validUntilOpt = mTimelines.front().validUntil();
-        if (validUntilOpt && *validUntilOpt < now) {
+        const bool isTimelineOutDated = FlagManager::getInstance().vrr_bugfix_24q4()
+                ? mTimelines.front().isWithin(now) == VsyncTimeline::VsyncOnTimeline::Outside
+                : validUntilOpt && *validUntilOpt < now;
+        if (isTimelineOutDated) {
             mTimelines.pop_front();
         } else {
             break;
@@ -569,46 +660,81 @@
     LOG_ALWAYS_FATAL_IF(mTimelines.back().validUntil().has_value());
 }
 
-VSyncPredictor::VsyncTimeline::VsyncTimeline(Period idealPeriod, std::optional<Fps> renderRateOpt)
-      : mIdealPeriod(idealPeriod), mRenderRateOpt(renderRateOpt) {}
+auto VSyncPredictor::VsyncTimeline::makeVsyncSequence(TimePoint knownVsync)
+        -> std::optional<VsyncSequence> {
+    if (knownVsync.ns() == 0) return std::nullopt;
+    return std::make_optional<VsyncSequence>({knownVsync.ns(), 0});
+}
+
+VSyncPredictor::VsyncTimeline::VsyncTimeline(TimePoint knownVsync, Period idealPeriod,
+                                             std::optional<Fps> renderRateOpt)
+      : mIdealPeriod(idealPeriod),
+        mRenderRateOpt(renderRateOpt),
+        mLastVsyncSequence(makeVsyncSequence(knownVsync)) {}
 
 void VSyncPredictor::VsyncTimeline::freeze(TimePoint lastVsync) {
     LOG_ALWAYS_FATAL_IF(mValidUntil.has_value());
-    ATRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
-                          mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
-                          float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
+    SFTRACE_FORMAT_INSTANT("renderRate %s valid for %.2f",
+                           mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA",
+                           float(lastVsync.ns() - TimePoint::now().ns()) / 1e6f);
     mValidUntil = lastVsync;
 }
 
 std::optional<TimePoint> VSyncPredictor::VsyncTimeline::nextAnticipatedVSyncTimeFrom(
-        Model model, Period minFramePeriod, nsecs_t vsync, MissedVsync missedVsync,
-        std::optional<nsecs_t> lastVsyncOpt) {
-    ATRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
+        Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsync,
+        MissedVsync missedVsync, std::optional<nsecs_t> lastVsyncOpt) {
+    SFTRACE_FORMAT("renderRate %s", mRenderRateOpt ? to_string(*mRenderRateOpt).c_str() : "NA");
 
+    nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
     const auto threshold = model.slope / 2;
     const auto lastFrameMissed =
             lastVsyncOpt && std::abs(*lastVsyncOpt - missedVsync.vsync.ns()) < threshold;
-    nsecs_t vsyncTime = snapToVsyncAlignedWithRenderRate(model, vsync);
-    nsecs_t vsyncFixupTime = 0;
-    if (FlagManager::getInstance().vrr_config() && lastFrameMissed) {
-        vsyncTime += missedVsync.fixup.ns();
-        ATRACE_FORMAT_INSTANT("lastFrameMissed");
-    } else {
-        vsyncFixupTime = getVsyncFixup(model, minFramePeriod, vsyncTime, lastVsyncOpt);
-        vsyncTime += vsyncFixupTime;
+    const auto mightBackpressure = minFramePeriodOpt && mRenderRateOpt &&
+            mRenderRateOpt->getPeriod() < 2 * (*minFramePeriodOpt);
+    if (FlagManager::getInstance().vrr_config()) {
+        if (lastFrameMissed) {
+            // If the last frame missed is the last vsync, we already shifted the timeline. Depends
+            // on whether we skipped the frame (onFrameMissed) or not (onFrameBegin) we apply a
+            // different fixup if we are violating the minFramePeriod.
+            // There is no need to shift the vsync timeline again.
+            if (vsyncTime - missedVsync.vsync.ns() < minFramePeriodOpt->ns()) {
+                vsyncTime += missedVsync.fixup.ns();
+                SFTRACE_FORMAT_INSTANT("lastFrameMissed");
+            }
+        } else if (mightBackpressure && lastVsyncOpt) {
+            if (!FlagManager::getInstance().vrr_bugfix_24q4()) {
+                // lastVsyncOpt does not need to be corrected with the new rate, and
+                // it should be used as is to avoid skipping a frame when changing rates are
+                // aligned at vsync time.
+                lastVsyncOpt = snapToVsyncAlignedWithRenderRate(model, *lastVsyncOpt);
+            }
+            const auto vsyncDiff = vsyncTime - *lastVsyncOpt;
+            if (vsyncDiff <= minFramePeriodOpt->ns() - threshold) {
+                // avoid a duplicate vsync
+                SFTRACE_FORMAT_INSTANT("skipping a vsync to avoid duplicate frame. next in %.2f "
+                                       "which "
+                                       "is %.2f "
+                                       "from "
+                                       "prev. "
+                                       "adjust by %.2f",
+                                       static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f,
+                                       static_cast<float>(vsyncDiff) / 1e6f,
+                                       static_cast<float>(mRenderRateOpt->getPeriodNsecs()) / 1e6f);
+                vsyncTime += mRenderRateOpt->getPeriodNsecs();
+            }
+        }
     }
 
-    ATRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
-    if (mValidUntil && vsyncTime > mValidUntil->ns()) {
-        ATRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
-                              static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
+    SFTRACE_FORMAT_INSTANT("vsync in %.2fms", float(vsyncTime - TimePoint::now().ns()) / 1e6f);
+    const bool isVsyncInvalid = FlagManager::getInstance().vrr_bugfix_24q4()
+            ? isWithin(TimePoint::fromNs(vsyncTime)) == VsyncOnTimeline::Outside
+            : mValidUntil && vsyncTime > mValidUntil->ns();
+    if (isVsyncInvalid) {
+        SFTRACE_FORMAT_INSTANT("no longer valid for vsync in %.2f",
+                               static_cast<float>(vsyncTime - TimePoint::now().ns()) / 1e6f);
         return std::nullopt;
     }
 
-    if (vsyncFixupTime > 0) {
-        shiftVsyncSequence(Duration::fromNs(vsyncFixupTime));
-    }
-
     return TimePoint::fromNs(vsyncTime);
 }
 
@@ -659,7 +785,9 @@
         return ticks<std::milli, float>(TimePoint::fromNs(timePoint) - now);
     };
 
-    Fps displayFps = mRenderRateOpt ? *mRenderRateOpt : Fps::fromPeriodNsecs(mIdealPeriod.ns());
+    Fps displayFps = !FlagManager::getInstance().vrr_bugfix_24q4() && mRenderRateOpt
+            ? *mRenderRateOpt
+            : Fps::fromPeriodNsecs(mIdealPeriod.ns());
     const auto divisor = RefreshRateSelector::getFrameRateDivisor(displayFps, frameRate);
     const auto now = TimePoint::now();
 
@@ -667,18 +795,39 @@
         return true;
     }
     const auto vsyncSequence = getVsyncSequenceLocked(model, vsync);
-    ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
-                          getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
+    SFTRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64 " divisor: %zu",
+                           getVsyncIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq, divisor);
     return vsyncSequence.seq % divisor == 0;
 }
 
-void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase) {
+void VSyncPredictor::VsyncTimeline::shiftVsyncSequence(Duration phase, Period minFramePeriod) {
     if (mLastVsyncSequence) {
-        ATRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
+        const auto renderRate = mRenderRateOpt.value_or(Fps::fromPeriodNsecs(mIdealPeriod.ns()));
+        const auto threshold = mIdealPeriod.ns() / 2;
+        if (renderRate.getPeriodNsecs() - phase.ns() + threshold >= minFramePeriod.ns()) {
+            SFTRACE_FORMAT_INSTANT("Not-Adjusting vsync by %.2f",
+                                   static_cast<float>(phase.ns()) / 1e6f);
+            return;
+        }
+        SFTRACE_FORMAT_INSTANT("adjusting vsync by %.2f", static_cast<float>(phase.ns()) / 1e6f);
         mLastVsyncSequence->vsyncTime += phase.ns();
     }
 }
 
+VSyncPredictor::VsyncTimeline::VsyncOnTimeline VSyncPredictor::VsyncTimeline::isWithin(
+        TimePoint vsync) {
+    const auto threshold = mIdealPeriod.ns() / 2;
+    if (!mValidUntil || vsync.ns() < mValidUntil->ns() - threshold) {
+        // if mValidUntil is absent then timeline is not frozen and
+        // vsync should be unique to that timeline.
+        return VsyncOnTimeline::Unique;
+    }
+    if (vsync.ns() > mValidUntil->ns() + threshold) {
+        return VsyncOnTimeline::Outside;
+    }
+    return VsyncOnTimeline::Shared;
+}
+
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index c175765..2df3d04 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -22,6 +22,7 @@
 #include <vector>
 
 #include <android-base/thread_annotations.h>
+#include <scheduler/FrameTime.h>
 #include <scheduler/TimeKeeper.h>
 #include <ui/DisplayId.h>
 
@@ -68,9 +69,16 @@
 
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
 
-    void setRenderRate(Fps) final EXCLUDES(mMutex);
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const EXCLUDES(mMutex) {
+        std::lock_guard lock(mMutex);
+        return mDisplayModePtr->getId() == modePtr->getId() &&
+                mDisplayModePtr->getVsyncRate().getPeriodNsecs() ==
+                mRateMap.find(idealPeriod())->second.slope;
+    }
 
-    void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
+    void setRenderRate(Fps, bool applyImmediately) final EXCLUDES(mMutex);
+
+    void onFrameBegin(TimePoint expectedPresentTime, FrameTime lastSignaledFrameTime) final
             EXCLUDES(mMutex);
     void onFrameMissed(TimePoint expectedPresentTime) final EXCLUDES(mMutex);
 
@@ -83,34 +91,43 @@
     };
 
     struct MissedVsync {
-        TimePoint vsync;
+        TimePoint vsync = TimePoint::fromNs(0);
         Duration fixup = Duration::fromNs(0);
     };
 
     class VsyncTimeline {
     public:
-        VsyncTimeline(Period idealPeriod, std::optional<Fps> renderRateOpt);
+        VsyncTimeline(TimePoint knownVsync, Period idealPeriod, std::optional<Fps> renderRateOpt);
         std::optional<TimePoint> nextAnticipatedVSyncTimeFrom(
-                Model model, Period minFramePeriod, nsecs_t vsyncTime, MissedVsync lastMissedVsync,
-                std::optional<nsecs_t> lastVsyncOpt = {});
+                Model model, std::optional<Period> minFramePeriodOpt, nsecs_t vsyncTime,
+                MissedVsync lastMissedVsync, std::optional<nsecs_t> lastVsyncOpt = {});
         void freeze(TimePoint lastVsync);
         std::optional<TimePoint> validUntil() const { return mValidUntil; }
         bool isVSyncInPhase(Model, nsecs_t vsync, Fps frameRate);
-        void shiftVsyncSequence(Duration phase);
+        void shiftVsyncSequence(Duration phase, Period minFramePeriod);
+        void setRenderRate(std::optional<Fps> renderRateOpt) { mRenderRateOpt = renderRateOpt; }
+
+        enum class VsyncOnTimeline {
+            Unique,  // Within timeline, not shared with next timeline.
+            Shared,  // Within timeline, shared with next timeline.
+            Outside, // Outside of the timeline.
+        };
+        VsyncOnTimeline isWithin(TimePoint vsync);
 
     private:
         nsecs_t snapToVsyncAlignedWithRenderRate(Model model, nsecs_t vsync);
         VsyncSequence getVsyncSequenceLocked(Model, nsecs_t vsync);
+        std::optional<VsyncSequence> makeVsyncSequence(TimePoint knownVsync);
 
         const Period mIdealPeriod = Duration::fromNs(0);
-        const std::optional<Fps> mRenderRateOpt;
+        std::optional<Fps> mRenderRateOpt;
         std::optional<TimePoint> mValidUntil;
         std::optional<VsyncSequence> mLastVsyncSequence;
     };
 
     VSyncPredictor(VSyncPredictor const&) = delete;
     VSyncPredictor& operator=(VSyncPredictor const&) = delete;
-    void clearTimestamps() REQUIRES(mMutex);
+    void clearTimestamps(bool clearTimelines) REQUIRES(mMutex);
 
     const std::unique_ptr<Clock> mClock;
     const PhysicalDisplayId mId;
@@ -143,6 +160,7 @@
     std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
 
     ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
+    int mNumVsyncsForFrame GUARDED_BY(mMutex);
 
     std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
 
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index 186a2d6..b974cd2 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -20,11 +20,10 @@
 //#define LOG_NDEBUG 0
 
 #include <assert.h>
+#include <common/trace.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
-#include <gui/TraceUtils.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include "../TracedOrdinal.h"
 #include "VSyncDispatch.h"
@@ -53,7 +52,7 @@
 VSyncReactor::~VSyncReactor() = default;
 
 bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!fence) {
         return false;
@@ -66,8 +65,8 @@
 
     std::lock_guard lock(mMutex);
     if (mExternalIgnoreFences || mInternalIgnoreFences) {
-        ATRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
-            mExternalIgnoreFences, mInternalIgnoreFences);
+        SFTRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
+                               mExternalIgnoreFences, mInternalIgnoreFences);
         return true;
     }
 
@@ -121,7 +120,7 @@
 }
 
 void VSyncReactor::startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr> modePtr) {
-    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
+    SFTRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mPeriodConfirmationInProgress = true;
     mModePtrTransitioningTo = modePtr.get();
     mMoreSamplesNeeded = true;
@@ -129,20 +128,21 @@
 }
 
 void VSyncReactor::endPeriodTransition() {
-    ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
+    SFTRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
     mModePtrTransitioningTo.reset();
     mPeriodConfirmationInProgress = false;
     mLastHwVsync.reset();
 }
 
 void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
-    ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
-                 modePtr->getVsyncRate().getPeriodNsecs());
+    SFTRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
+                  modePtr->getVsyncRate().getPeriodNsecs());
     std::lock_guard lock(mMutex);
     mLastHwVsync.reset();
 
-    if (!mSupportKernelIdleTimer &&
-        modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) {
+    // kernel idle timer is not applicable for VRR
+    const bool supportKernelIdleTimer = mSupportKernelIdleTimer && !modePtr->getVrrConfig();
+    if (!supportKernelIdleTimer && mTracker.isCurrentMode(modePtr) && !force) {
         endPeriodTransition();
         setIgnorePresentFencesInternal(false);
         mMoreSamplesNeeded = false;
@@ -192,7 +192,7 @@
 
     std::lock_guard lock(mMutex);
     if (periodConfirmed(timestamp, hwcVsyncPeriod)) {
-        ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
         if (mModePtrTransitioningTo) {
             mTracker.setDisplayModePtr(ftl::as_non_null(mModePtrTransitioningTo));
             *periodFlushed = true;
@@ -206,17 +206,22 @@
         endPeriodTransition();
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     } else if (mPeriodConfirmationInProgress) {
-        ATRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": still confirming period", mId.value);
         mLastHwVsync = timestamp;
         mMoreSamplesNeeded = true;
         *periodFlushed = false;
     } else {
-        ATRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value);
+        SFTRACE_FORMAT("VSR %" PRIu64 ": adding sample", mId.value);
         *periodFlushed = false;
         mTracker.addVsyncTimestamp(timestamp);
         mMoreSamplesNeeded = mTracker.needsMoreSamples();
     }
 
+    if (mExternalIgnoreFences) {
+      // keep HWVSync on as long as we ignore present fences.
+      mMoreSamplesNeeded = true;
+    }
+
     if (!mMoreSamplesNeeded) {
         setIgnorePresentFencesInternal(false);
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 1e55a87..3376fad 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -21,6 +21,7 @@
 
 #include <scheduler/Fps.h>
 #include <scheduler/FrameRateMode.h>
+#include <scheduler/FrameTime.h>
 
 #include "VSyncDispatch.h"
 
@@ -71,6 +72,11 @@
      */
     virtual Period minFramePeriod() const = 0;
 
+    /**
+     * Checks if the sourced mode is equal to the mode in the tracker.
+     */
+    virtual bool isCurrentMode(const ftl::NonNull<DisplayModePtr>& modePtr) const = 0;
+
     /* Inform the tracker that the samples it has are not accurate for prediction. */
     virtual void resetModel() = 0;
 
@@ -102,11 +108,12 @@
      * when a display is running at 120Hz but the render frame rate is 60Hz.
      *
      * \param [in] Fps   The render rate the tracker should operate at.
+     * \param [in] applyImmediately Whether to apply the new render rate immediately regardless of
+     *                              already committed vsyncs.
      */
-    virtual void setRenderRate(Fps) = 0;
+    virtual void setRenderRate(Fps, bool applyImmediately) = 0;
 
-    virtual void onFrameBegin(TimePoint expectedPresentTime,
-                              TimePoint lastConfirmedPresentTime) = 0;
+    virtual void onFrameBegin(TimePoint expectedPresentTime, FrameTime lastSignaledFrameTime) = 0;
 
     virtual void onFrameMissed(TimePoint expectedPresentTime) = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 586357f..3c5f68c 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -21,9 +21,8 @@
 
 #include "VsyncModulator.h"
 
-#include <android-base/properties.h>
+#include <common/trace.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <chrono>
 #include <cinttypes>
@@ -37,8 +36,7 @@
 
 VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now)
       : mVsyncConfigSet(config),
-        mNow(now),
-        mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {}
+        mNow(now) {}
 
 VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) {
     std::lock_guard<std::mutex> lock(mMutex);
@@ -71,10 +69,6 @@
             break;
     }
 
-    if (mTraceDetailedInfo) {
-        ATRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
-    }
-
     if (mEarlyWakeupRequests.empty() && schedule == Schedule::EarlyEnd) {
         mEarlyTransactionFrames = MIN_EARLY_TRANSACTION_FRAMES;
         mEarlyTransactionStartTime = mNow();
@@ -167,15 +161,19 @@
     const VsyncConfig& offsets = getNextVsyncConfig();
     mVsyncConfig = offsets;
 
-    if (mTraceDetailedInfo) {
-        const bool isEarly = &offsets == &mVsyncConfigSet.early;
-        const bool isEarlyGpu = &offsets == &mVsyncConfigSet.earlyGpu;
-        const bool isLate = &offsets == &mVsyncConfigSet.late;
+    // Trace config type
+    SFTRACE_INT("Vsync-Early",  &mVsyncConfig == &mVsyncConfigSet.early);
+    SFTRACE_INT("Vsync-EarlyGpu", &mVsyncConfig == &mVsyncConfigSet.earlyGpu);
+    SFTRACE_INT("Vsync-Late", &mVsyncConfig == &mVsyncConfigSet.late);
 
-        ATRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
-        ATRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
-        ATRACE_INT("Vsync-LateOffsetsOn", isLate);
-    }
+    // Trace early vsync conditions
+    SFTRACE_INT("EarlyWakeupRequests",
+                                 static_cast<int>(mEarlyWakeupRequests.size()));
+    SFTRACE_INT("EarlyTransactionFrames", mEarlyTransactionFrames);
+    SFTRACE_INT("RefreshRateChangePending", mRefreshRateChangePending);
+
+    // Trace early gpu conditions
+    SFTRACE_INT("EarlyGpuFrames", mEarlyGpuFrames);
 
     return offsets;
 }
@@ -183,7 +181,6 @@
 void VsyncModulator::binderDied(const wp<IBinder>& who) {
     std::lock_guard<std::mutex> lock(mMutex);
     mEarlyWakeupRequests.erase(who);
-
     static_cast<void>(updateVsyncConfigLocked());
 }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index be0d334..d0a7935 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -105,7 +105,6 @@
     std::atomic<TimePoint> mLastTransactionCommitTime = TimePoint();
 
     const Now mNow;
-    const bool mTraceDetailedInfo;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 2fa3318..d3e312a 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -18,8 +18,8 @@
 
 #include <common/FlagManager.h>
 
+#include <common/trace.h>
 #include <ftl/fake_guard.h>
-#include <gui/TraceUtils.h>
 #include <scheduler/Fps.h>
 #include <scheduler/Timer.h>
 
@@ -182,7 +182,7 @@
 }
 
 void VsyncSchedule::enableHardwareVsyncLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mHwVsyncState == HwVsyncState::Disabled) {
         getTracker().resetModel();
         mRequestHardwareVsync(mId, true);
@@ -191,7 +191,7 @@
 }
 
 void VsyncSchedule::disableHardwareVsync(bool disallow) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mHwVsyncLock);
     switch (mHwVsyncState) {
         case HwVsyncState::Enabled:
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
index 59a6df2..f2be316 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h
@@ -36,8 +36,11 @@
 };
 
 inline std::string to_string(const FrameRateMode& mode) {
-    return to_string(mode.fps) + " (" + to_string(mode.modePtr->getPeakFps()) + "(" +
-            to_string(mode.modePtr->getVsyncRate()) + "))";
+    return base::StringPrintf("{fps=%s, modePtr={id=%d, vsyncRate=%s, peakRefreshRate=%s}}",
+                              to_string(mode.fps).c_str(),
+                              ftl::to_underlying(mode.modePtr->getId()),
+                              to_string(mode.modePtr->getVsyncRate()).c_str(),
+                              to_string(mode.modePtr->getPeakFps()).c_str());
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
index a5bb6c2..2185bb0 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -26,6 +26,7 @@
 #include <ui/FenceTime.h>
 
 #include <scheduler/Features.h>
+#include <scheduler/FrameTime.h>
 #include <scheduler/Time.h>
 #include <scheduler/VsyncId.h>
 #include <scheduler/interface/CompositeResult.h>
@@ -33,6 +34,7 @@
 // TODO(b/185536303): Pull to FTL.
 #include "../../../TracedOrdinal.h"
 #include "../../../Utils/Dumper.h"
+#include "../../../Utils/RingBuffer.h"
 
 namespace android::scheduler {
 
@@ -53,36 +55,31 @@
 
     std::optional<TimePoint> earliestPresentTime() const { return mEarliestPresentTime; }
 
-    // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
-    TimePoint pastVsyncTime(Period minFramePeriod) const;
-
-    // The present fence for the frame that had targeted the most recent VSYNC before this frame.
-    // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
-    // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
-    // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
-    // signaled by now (unless that frame missed).
-    const FenceTimePtr& presentFenceForPastVsync(Period minFramePeriod) const;
-
-    // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
-    const FenceTimePtr& presentFenceForPreviousFrame() const {
-        return mPresentFences.front().fenceTime;
-    }
+    // Equivalent to `expectedSignaledPresentFence` unless running N VSYNCs ahead.
+    const FenceTimePtr& presentFenceForPreviousFrame() const;
 
     bool isFramePending() const { return mFramePending; }
+    bool wouldBackpressureHwc() const { return mWouldBackpressureHwc; }
     bool didMissFrame() const { return mFrameMissed; }
     bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
+    FrameTime lastSignaledFrameTime() const { return mLastSignaledFrameTime; }
 
 protected:
     explicit FrameTarget(const std::string& displayLabel);
     ~FrameTarget() = default;
 
-    bool wouldPresentEarly(Period minFramePeriod) const;
+    bool wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const;
 
     // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
     TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
         return mExpectedPresentTime - minFramePeriod;
     }
 
+    void addFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime,
+                  TimePoint expectedPresentTime) {
+        mPresentFences.next() = {std::move(presentFence), presentFenceTime, expectedPresentTime};
+    }
+
     VsyncId mVsyncId;
     TimePoint mFrameBeginTime;
     TimePoint mExpectedPresentTime;
@@ -92,12 +89,25 @@
     TracedOrdinal<bool> mFrameMissed;
     TracedOrdinal<bool> mHwcFrameMissed;
     TracedOrdinal<bool> mGpuFrameMissed;
+    bool mWouldBackpressureHwc = false;
 
-    struct FenceWithFenceTime {
+    struct PresentFence {
         sp<Fence> fence = Fence::NO_FENCE;
         FenceTimePtr fenceTime = FenceTime::NO_FENCE;
+        TimePoint expectedPresentTime = TimePoint();
     };
-    std::array<FenceWithFenceTime, 2> mPresentFences;
+
+    // The present fence for the frame that had targeted the most recent VSYNC before this frame.
+    // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
+    // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
+    // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
+    // signaled by now (unless that frame missed).
+    std::pair<bool /* wouldBackpressure */, PresentFence> expectedSignaledPresentFence(
+            Period vsyncPeriod, Period minFramePeriod) const;
+    std::array<PresentFence, 2> mPresentFencesLegacy;
+    utils::RingBuffer<PresentFence, 5> mPresentFences;
+
+    FrameTime mLastSignaledFrameTime;
 
 private:
     friend class FrameTargeterTestBase;
@@ -129,7 +139,7 @@
 
     void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
 
-    std::optional<TimePoint> computeEarliestPresentTime(Period minFramePeriod,
+    std::optional<TimePoint> computeEarliestPresentTime(Period vsyncPeriod, Period minFramePeriod,
                                                         Duration hwcMinWorkDuration);
 
     // TODO(b/241285191): Merge with FrameTargeter::endFrame.
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTime.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTime.h
new file mode 100644
index 0000000..ed5c899
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTime.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <scheduler/Time.h>
+
+namespace android::scheduler {
+struct FrameTime {
+    TimePoint signalTime;
+    TimePoint expectedPresentTime;
+};
+} // namespace android::scheduler
\ No newline at end of file
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
index 68c277d..3ee1e54 100644
--- a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-#include <gui/TraceUtils.h>
-
+#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <scheduler/FrameTargeter.h>
 #include <scheduler/IVsyncSource.h>
+#include <utils/Log.h>
 
 namespace android::scheduler {
+using namespace std::chrono_literals;
 
 FrameTarget::FrameTarget(const std::string& displayLabel)
       : mFramePending("PrevFramePending " + displayLabel, false),
@@ -27,29 +29,53 @@
         mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
         mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
 
-TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
-    const int shift = static_cast<int>(targetsVsyncsAhead<2>(minFramePeriod));
-    return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
+std::pair<bool /* wouldBackpressure */, FrameTarget::PresentFence>
+FrameTarget::expectedSignaledPresentFence(Period vsyncPeriod, Period minFramePeriod) const {
+    if (!FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
+        return {true, mPresentFencesLegacy[i]};
+    }
+
+    bool wouldBackpressure = true;
+    auto expectedPresentTime = mExpectedPresentTime;
+    for (size_t i = mPresentFences.size(); i != 0; --i) {
+        const auto& fence = mPresentFences[i - 1];
+
+        if (fence.expectedPresentTime + minFramePeriod < expectedPresentTime - vsyncPeriod / 2) {
+            wouldBackpressure = false;
+        }
+
+        if (fence.expectedPresentTime <= mFrameBeginTime) {
+            return {wouldBackpressure, fence};
+        }
+
+        expectedPresentTime = fence.expectedPresentTime;
+    }
+    return {wouldBackpressure, PresentFence{}};
 }
 
-const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
-    // TODO(b/267315508): Generalize to N VSYNCs.
-    const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
-    return mPresentFences[i].fenceTime;
-}
-
-bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const {
-    // TODO(b/241285475): Since this is called during `composite`, the calls to `targetsVsyncsAhead`
-    // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
-
-    // TODO(b/267315508): Generalize to N VSYNCs.
+bool FrameTarget::wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const {
     if (targetsVsyncsAhead<3>(minFramePeriod)) {
         return true;
     }
 
-    const auto fence = presentFenceForPastVsync(minFramePeriod);
-    return fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
+    const auto [wouldBackpressure, fence] =
+            expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
+
+    return !wouldBackpressure ||
+            (fence.fenceTime->isValid() &&
+             fence.fenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING);
+}
+
+const FenceTimePtr& FrameTarget::presentFenceForPreviousFrame() const {
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        if (mPresentFences.size() > 0) {
+            return mPresentFences.back().fenceTime;
+        }
+        return FenceTime::NO_FENCE;
+    }
+
+    return mPresentFencesLegacy.front().fenceTime;
 }
 
 void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource) {
@@ -83,27 +109,39 @@
     }
 
     if (!mSupportsExpectedPresentTime) {
-        mEarliestPresentTime = computeEarliestPresentTime(minFramePeriod, args.hwcMinWorkDuration);
+        mEarliestPresentTime =
+                computeEarliestPresentTime(vsyncPeriod, minFramePeriod, args.hwcMinWorkDuration);
     }
 
-    ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
-                  ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
-                  mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
+    SFTRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
+                   ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
+                   mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
 
-    const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(minFramePeriod);
+    const auto [wouldBackpressure, fence] =
+            expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
 
     // In cases where the present fence is about to fire, give it a small grace period instead of
     // giving up on the frame.
-    //
-    // TODO(b/280667110): The grace period should depend on `sfWorkDuration` and `vsyncPeriod` being
-    // approximately equal, not whether backpressure propagation is enabled.
-    const int graceTimeForPresentFenceMs = static_cast<int>(
-            mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
+    const int graceTimeForPresentFenceMs = [&] {
+        const bool considerBackpressure =
+                mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+        if (!FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+            return static_cast<int>(considerBackpressure);
+        }
+
+        if (!wouldBackpressure || !considerBackpressure) {
+            return 0;
+        }
+
+        return static_cast<int>((std::abs(fence.expectedPresentTime.ns() - mFrameBeginTime.ns()) <=
+                                 Duration(1ms).ns()));
+    }();
 
     // Pending frames may trigger backpressure propagation.
     const auto& isFencePending = *isFencePendingFuncPtr;
-    mFramePending = pastPresentFence != FenceTime::NO_FENCE &&
-            isFencePending(pastPresentFence, graceTimeForPresentFenceMs);
+    mFramePending = fence.fenceTime != FenceTime::NO_FENCE &&
+            isFencePending(fence.fenceTime, graceTimeForPresentFenceMs);
 
     // A frame is missed if the prior frame is still pending. If no longer pending, then we still
     // count the frame as missed if the predicted present time was further in the past than when the
@@ -111,8 +149,10 @@
     // than a typical frame duration, but should not be so small that it reports reasonable drift as
     // a missed frame.
     mFrameMissed = mFramePending || [&] {
-        const nsecs_t pastPresentTime = pastPresentFence->getSignalTime();
+        const nsecs_t pastPresentTime = fence.fenceTime->getSignalTime();
         if (pastPresentTime < 0) return false;
+        mLastSignaledFrameTime = {.signalTime = TimePoint::fromNs(pastPresentTime),
+                                  .expectedPresentTime = fence.expectedPresentTime};
         const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
         return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
     }();
@@ -123,11 +163,14 @@
     if (mFrameMissed) mFrameMissedCount++;
     if (mHwcFrameMissed) mHwcFrameMissedCount++;
     if (mGpuFrameMissed) mGpuFrameMissedCount++;
+
+    mWouldBackpressureHwc = mFramePending && wouldBackpressure;
 }
 
-std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period minFramePeriod,
+std::optional<TimePoint> FrameTargeter::computeEarliestPresentTime(Period vsyncPeriod,
+                                                                   Period minFramePeriod,
                                                                    Duration hwcMinWorkDuration) {
-    if (wouldPresentEarly(minFramePeriod)) {
+    if (wouldPresentEarly(vsyncPeriod, minFramePeriod)) {
         return previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
     }
     return {};
@@ -143,8 +186,12 @@
 }
 
 FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime) {
-    mPresentFences[1] = mPresentFences[0];
-    mPresentFences[0] = {std::move(presentFence), presentFenceTime};
+    if (FlagManager::getInstance().allow_n_vsyncs_in_targeter()) {
+        addFence(std::move(presentFence), presentFenceTime, mExpectedPresentTime);
+    } else {
+        mPresentFencesLegacy[1] = mPresentFencesLegacy[0];
+        mPresentFencesLegacy[0] = {std::move(presentFence), presentFenceTime, mExpectedPresentTime};
+    }
     return presentFenceTime;
 }
 
@@ -156,7 +203,7 @@
 }
 
 bool FrameTargeter::isFencePending(const FenceTimePtr& fence, int graceTimeMs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     const status_t status = fence->wait(graceTimeMs);
 
     // This is the same as Fence::Status::Unsignaled, but it saves a call to getStatus,
diff --git a/services/surfaceflinger/Scheduler/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index eeb9c60..20c58eb 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -24,10 +24,10 @@
 #include <sys/timerfd.h>
 #include <sys/unistd.h>
 
+#include <common/trace.h>
 #include <ftl/concat.h>
 #include <ftl/enum.h>
 #include <log/log.h>
-#include <utils/Trace.h>
 
 #include <scheduler/Timer.h>
 
@@ -188,9 +188,9 @@
         int nfds = epoll_wait(mEpollFd, events, DispatchType::MAX_DISPATCH_TYPE, -1);
 
         setDebugState(DebugState::Running);
-        if (ATRACE_ENABLED()) {
+        if (SFTRACE_ENABLED()) {
             ftl::Concat trace("TimerIteration #", iteration++);
-            ATRACE_NAME(trace.c_str());
+            SFTRACE_NAME(trace.c_str());
         }
 
         if (nfds == -1) {
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
index 29711af..6f4e1f1 100644
--- a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -24,6 +24,7 @@
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 
+using namespace com::android::graphics::surfaceflinger;
 using namespace std::chrono_literals;
 
 namespace android::scheduler {
@@ -52,8 +53,13 @@
 
     const auto& target() const { return mTargeter.target(); }
 
-    bool wouldPresentEarly(Period minFramePeriod) const {
-        return target().wouldPresentEarly(minFramePeriod);
+    bool wouldPresentEarly(Period vsyncPeriod, Period minFramePeriod) const {
+        return target().wouldPresentEarly(vsyncPeriod, minFramePeriod);
+    }
+
+    std::pair<bool /*wouldBackpressure*/, FrameTarget::PresentFence> expectedSignaledPresentFence(
+            Period vsyncPeriod, Period minFramePeriod) const {
+        return target().expectedSignaledPresentFence(vsyncPeriod, minFramePeriod);
     }
 
     struct Frame {
@@ -175,11 +181,68 @@
     constexpr Duration kFrameDuration = 13ms;
 
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
-        const auto fence = frame.end();
+        FenceTimePtr fence;
+        {
+            Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+                        kRefreshRate);
+            fence = frame.end();
+        }
 
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
-        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), fence);
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        ASSERT_TRUE(wouldBackpressure);
+        EXPECT_EQ(presentFence.fenceTime, fence);
+    }
+}
+
+TEST_F(FrameTargeterTest, wouldBackpressureAfterTime) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
+    VsyncId vsyncId{111};
+    TimePoint frameBeginTime(1000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 13ms;
+
+    { Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate); }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
+    }
+    {
+        frameBeginTime += kPeriod;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_FALSE(wouldBackpressure);
+    }
+}
+
+TEST_F(FrameTargeterTest, wouldBackpressureAfterTimeLegacy) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, false);
+    VsyncId vsyncId{111};
+    TimePoint frameBeginTime(1000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 13ms;
+
+    { Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate); }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
+    }
+    {
+        frameBeginTime += kPeriod;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto [wouldBackpressure, presentFence] =
+                expectedSignaledPresentFence(kPeriod, kPeriod);
+        EXPECT_TRUE(wouldBackpressure);
     }
 }
 
@@ -191,46 +254,63 @@
     constexpr Duration kFrameDuration = 10ms;
 
     FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr currentFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
         Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
-        const auto fence = frame.end();
-
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
-        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
-
-        previousFence = fence;
+        EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, previousFence);
+        previousFence = currentFence;
+        currentFence = frame.end();
     }
 }
 
-TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
-    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+TEST_F(FrameTargeterTest, recallsPastVsyncFiveVsyncsAhead) {
+    SET_FLAG_FOR_TEST(flags::allow_n_vsyncs_in_targeter, true);
 
     VsyncId vsyncId{222};
     TimePoint frameBeginTime(2000ms);
     constexpr Fps kRefreshRate = 120_Hz;
-    constexpr Fps kPeakRefreshRate = 240_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Duration kFrameDuration = 40ms;
+
+    FenceTimePtr firstFence = FenceTime::NO_FENCE;
+    for (int n = 5; n-- > 0;) {
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+        if (firstFence == FenceTime::NO_FENCE) {
+            firstFence = fence;
+        }
+    }
+
+    Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+    EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, firstFence);
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    VsyncId vsyncId{222};
+    TimePoint frameBeginTime(2000ms);
+    constexpr Fps kRefreshRate = 120_Hz;
+    constexpr Fps kVsyncRate = 240_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+    constexpr Period kVsyncPeriod = kVsyncRate.getPeriod();
     constexpr Duration kFrameDuration = 10ms;
 
     FenceTimePtr previousFence = FenceTime::NO_FENCE;
-
+    FenceTimePtr currentFence = FenceTime::NO_FENCE;
     for (int n = 5; n-- > 0;) {
-        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
-                    kPeakRefreshRate);
-        const auto fence = frame.end();
-
-        EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
-        EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
-
-        previousFence = fence;
+        Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+        EXPECT_EQ(expectedSignaledPresentFence(kVsyncPeriod, kPeriod).second.fenceTime,
+                  previousFence);
+        previousFence = currentFence;
+        currentFence = frame.end();
     }
 }
 
 TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
     constexpr Period kPeriod = (60_Hz).getPeriod();
-    EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
-    EXPECT_FALSE(wouldPresentEarly(kPeriod));
+    EXPECT_EQ(expectedSignaledPresentFence(kPeriod, kPeriod).second.fenceTime, FenceTime::NO_FENCE);
+    EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
 }
 
 TEST_F(FrameTargeterTest, detectsEarlyPresent) {
@@ -241,20 +321,57 @@
 
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // The target is early if the past present fence was signaled.
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
 
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // `finalFrame` would present early, so it has an earliest present time.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
+    ASSERT_NE(std::nullopt, target().earliestPresentTime());
+    EXPECT_EQ(*target().earliestPresentTime(),
+              target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentAfterLongPeriod) {
+    VsyncId vsyncId{333};
+    TimePoint frameBeginTime(3000ms);
+    constexpr Fps kRefreshRate = 60_Hz;
+    constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+    // The target is not early while past present fences are pending.
+    for (int n = 3; n-- > 0;) {
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
+        EXPECT_FALSE(target().earliestPresentTime());
+    }
+
+    // The target is early if the past present fence was signaled.
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
+
+    frameBeginTime += 10 * kPeriod;
+
+    Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+    // `finalFrame` would present early, so it has an earliest present time.
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_NE(std::nullopt, target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
@@ -270,21 +387,26 @@
 
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
 
     // The target is early if the past present fence was signaled.
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
 
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // `finalFrame` would present early, but we have expected present time support, so it has no
     // earliest present time.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_EQ(std::nullopt, target().earliestPresentTime());
 }
 
@@ -296,17 +418,21 @@
 
     // The target is not early while past present fences are pending.
     for (int n = 3; n-- > 0;) {
-        const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-        EXPECT_FALSE(wouldPresentEarly(kPeriod));
+        {
+            const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+        }
+        EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
         EXPECT_FALSE(target().earliestPresentTime());
     }
+    {
+        Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
-    Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
-    const auto fence = frame.end();
-    fence->signalForTest(frameBeginTime.ns());
+        const auto fence = frame.end();
+        fence->signalForTest(frameBeginTime.ns());
+    }
 
     // The target is two VSYNCs ahead, so the past present fence is still pending.
-    EXPECT_FALSE(wouldPresentEarly(kPeriod));
+    EXPECT_FALSE(wouldPresentEarly(kPeriod, kPeriod));
     EXPECT_FALSE(target().earliestPresentTime());
 
     { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
@@ -314,7 +440,7 @@
     Frame finalFrame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
 
     // The target is early if the past present fence was signaled.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     ASSERT_NE(std::nullopt, target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
@@ -325,10 +451,10 @@
     constexpr Fps kRefreshRate = 144_Hz;
     constexpr Period kPeriod = kRefreshRate.getPeriod();
 
-    const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
+    { const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate); }
 
     // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
-    EXPECT_TRUE(wouldPresentEarly(kPeriod));
+    EXPECT_TRUE(wouldPresentEarly(kPeriod, kPeriod));
     EXPECT_TRUE(target().earliestPresentTime());
     EXPECT_EQ(*target().earliestPresentTime(),
               target().expectedPresentTime() - kPeriod - kHwcMinWorkDuration);
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index dd03366..41a9a1b 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -30,7 +30,7 @@
             ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
             const compositionengine::Output::ColorProfile&,
             bool>(args.compositionEngine, args.renderArea, args.colorProfile, args.regionSampling,
-                  args.dimInGammaSpaceForEnhancedScreenshots);
+                  args.dimInGammaSpaceForEnhancedScreenshots, args.enableLocalTonemapping);
     output->editState().isSecure = args.renderArea.isSecure();
     output->editState().isProtected = args.isProtected;
     output->setCompositionEnabled(true);
@@ -63,11 +63,13 @@
 
 ScreenCaptureOutput::ScreenCaptureOutput(
         const RenderArea& renderArea, const compositionengine::Output::ColorProfile& colorProfile,
-        bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots)
+        bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots,
+        bool enableLocalTonemapping)
       : mRenderArea(renderArea),
         mColorProfile(colorProfile),
         mRegionSampling(regionSampling),
-        mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots) {}
+        mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots),
+        mEnableLocalTonemapping(enableLocalTonemapping) {}
 
 void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
     auto& outputState = editState();
@@ -88,6 +90,17 @@
                 aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
     }
 
+    if (mEnableLocalTonemapping) {
+        clientCompositionDisplay.tonemapStrategy =
+                renderengine::DisplaySettings::TonemapStrategy::Local;
+        if (static_cast<ui::PixelFormat>(buffer->getPixelFormat()) == ui::PixelFormat::RGBA_FP16) {
+            clientCompositionDisplay.targetHdrSdrRatio =
+                    getState().displayBrightnessNits / getState().sdrWhitePointNits;
+        } else {
+            clientCompositionDisplay.targetHdrSdrRatio = 1.f;
+        }
+    }
+
     return clientCompositionDisplay;
 }
 
diff --git a/services/surfaceflinger/ScreenCaptureOutput.h b/services/surfaceflinger/ScreenCaptureOutput.h
index 069f458..c233ead 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.h
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -39,6 +39,7 @@
     bool treat170mAsSrgb;
     bool dimInGammaSpaceForEnhancedScreenshots;
     bool isProtected = false;
+    bool enableLocalTonemapping = false;
 };
 
 // ScreenCaptureOutput is used to compose a set of layers into a preallocated buffer.
@@ -49,7 +50,8 @@
 public:
     ScreenCaptureOutput(const RenderArea& renderArea,
                         const compositionengine::Output::ColorProfile& colorProfile,
-                        bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots);
+                        bool regionSampling, bool dimInGammaSpaceForEnhancedScreenshots,
+                        bool enableLocalTonemapping);
 
     void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
 
@@ -67,6 +69,7 @@
     const compositionengine::Output::ColorProfile& mColorProfile;
     const bool mRegionSampling;
     const bool mDimInGammaSpaceForEnhancedScreenshots;
+    const bool mEnableLocalTonemapping;
 };
 
 std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs);
diff --git a/services/surfaceflinger/StartPropertySetThread.cpp b/services/surfaceflinger/StartPropertySetThread.cpp
deleted file mode 100644
index f42cd53..0000000
--- a/services/surfaceflinger/StartPropertySetThread.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cutils/properties.h>
-#include "StartPropertySetThread.h"
-
-namespace android {
-
-StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
-        Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
-
-status_t StartPropertySetThread::Start() {
-    return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
-}
-
-bool StartPropertySetThread::threadLoop() {
-    // Set property service.sf.present_timestamp, consumer need check its readiness
-    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
-    // Clear BootAnimation exit flag
-    property_set("service.bootanim.exit", "0");
-    property_set("service.bootanim.progress", "0");
-    // Start BootAnimation if not started
-    property_set("ctl.start", "bootanim");
-    // Exit immediately
-    return false;
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/StartPropertySetThread.h b/services/surfaceflinger/StartPropertySetThread.h
deleted file mode 100644
index bbdcde2..0000000
--- a/services/surfaceflinger/StartPropertySetThread.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_STARTBOOTANIMTHREAD_H
-#define ANDROID_STARTBOOTANIMTHREAD_H
-
-#include <stddef.h>
-
-#include <utils/Mutex.h>
-#include <utils/Thread.h>
-
-namespace android {
-
-class StartPropertySetThread : public Thread {
-// Boot animation is triggered via calls to "property_set()" which can block
-// if init's executing slow operation such as 'mount_all --late' (currently
-// happening 1/10th with fsck)  concurrently. Running in a separate thread
-// allows to pursue the SurfaceFlinger's init process without blocking.
-// see b/34499826.
-// Any property_set() will block during init stage so need to be offloaded
-// to this thread. see b/63844978.
-public:
-    explicit StartPropertySetThread(bool timestampPropertyValue);
-    status_t Start();
-private:
-    virtual bool threadLoop();
-    static constexpr const char* kTimestampProperty = "service.sf.present_timestamp";
-    const bool mTimestampPropertyValue;
-};
-
-}
-
-#endif // ANDROID_STARTBOOTANIMTHREAD_H
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 0f8e3bf..326bf57 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -40,6 +40,10 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
+#include <com_android_graphics_libgui_flags.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
@@ -55,23 +59,22 @@
 #include <configstore/Utils.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
+#include <fmt/format.h>
 #include <ftl/algorithm.h>
 #include <ftl/concat.h>
 #include <ftl/fake_guard.h>
 #include <ftl/future.h>
 #include <ftl/unit.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
 #include <gui/IProducerListener.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/LayerMetadata.h>
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
-#include <gui/TraceUtils.h>
 #include <hidl/ServiceManagement.h>
-#include <layerproto/LayerProtoParser.h>
+#include <layerproto/LayerProtoHeader.h>
 #include <linux/sched/types.h>
 #include <log/log.h>
 #include <private/android_filesystem_config.h>
@@ -141,6 +144,7 @@
 #include "FrontEnd/LayerLog.h"
 #include "FrontEnd/LayerSnapshot.h"
 #include "HdrLayerInfoReporter.h"
+#include "Jank/JankTracker.h"
 #include "Layer.h"
 #include "LayerProtoHelper.h"
 #include "LayerRenderArea.h"
@@ -148,13 +152,13 @@
 #include "MutexUtils.h"
 #include "NativeWindowSurface.h"
 #include "RegionSamplingThread.h"
+#include "RenderAreaBuilder.h"
 #include "Scheduler/EventThread.h"
 #include "Scheduler/LayerHistory.h"
 #include "Scheduler/Scheduler.h"
 #include "Scheduler/VsyncConfiguration.h"
 #include "Scheduler/VsyncModulator.h"
 #include "ScreenCaptureOutput.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
 #include "TunnelModeEnabledReporter.h"
@@ -169,10 +173,6 @@
 #define NO_THREAD_SAFETY_ANALYSIS \
     _Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
 
-// To enable layer borders in the system, change the below flag to true.
-#undef DOES_CONTAIN_BORDER
-#define DOES_CONTAIN_BORDER false
-
 namespace android {
 using namespace std::chrono_literals;
 using namespace std::string_literals;
@@ -206,8 +206,6 @@
 using ui::DisplayPrimaries;
 using ui::RenderIntent;
 
-using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
-
 namespace hal = android::hardware::graphics::composer::hal;
 
 namespace {
@@ -233,7 +231,7 @@
     return dataspace == Dataspace::V0_SRGB || dataspace == Dataspace::DISPLAY_P3;
 }
 
-std::chrono::milliseconds getIdleTimerTimeout(DisplayId displayId) {
+std::chrono::milliseconds getIdleTimerTimeout(PhysicalDisplayId displayId) {
     if (const int32_t displayIdleTimerMs =
                 base::GetIntProperty("debug.sf.set_idle_timer_ms_"s +
                                              std::to_string(displayId.value),
@@ -247,7 +245,7 @@
     return std::chrono::milliseconds(millis);
 }
 
-bool getKernelIdleTimerSyspropConfig(DisplayId displayId) {
+bool getKernelIdleTimerSyspropConfig(PhysicalDisplayId displayId) {
     const bool displaySupportKernelIdleTimer =
             base::GetBoolProperty("debug.sf.support_kernel_idle_timer_"s +
                                           std::to_string(displayId.value),
@@ -375,8 +373,6 @@
 const String16 sInternalSystemWindow("android.permission.INTERNAL_SYSTEM_WINDOW");
 const String16 sWakeupSurfaceFlinger("android.permission.WAKEUP_SURFACE_FLINGER");
 
-const char* KERNEL_IDLE_TIMER_PROP = "graphics.display.kernel_idle_timer.enabled";
-
 // ---------------------------------------------------------------------------
 int64_t SurfaceFlinger::dispSyncPresentTimeOffset;
 bool SurfaceFlinger::useHwcForRgbToYuv;
@@ -429,12 +425,13 @@
         mInternalDisplayDensity(
                 getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)),
         mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
-        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()) {
+        mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()),
+        mSkipPowerOnForQuiescent(base::GetBoolProperty("ro.boot.quiescent"s, false)) {
     ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
 }
 
 SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipInitialization) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGI("SurfaceFlinger is starting");
 
     hasSyncFramework = running_without_sync_framework(true);
@@ -534,15 +531,7 @@
 
     mIgnoreHdrCameraLayers = ignore_hdr_camera_layers(false);
 
-    mLayerLifecycleManagerEnabled =
-            base::GetBoolProperty("persist.debug.sf.enable_layer_lifecycle_manager"s, true);
-    mLegacyFrontEndEnabled = !mLayerLifecycleManagerEnabled ||
-            base::GetBoolProperty("persist.debug.sf.enable_legacy_frontend"s, false);
-
     // These are set by the HWC implementation to indicate that they will use the workarounds.
-    mIsHotplugErrViaNegVsync =
-            base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
-
     mIsHdcpViaNegVsync = base::GetBoolProperty("debug.sf.hwc_hdcp_via_neg_vsync"s, false);
 }
 
@@ -567,20 +556,25 @@
         initializeDisplays();
     }));
 
-    startBootAnim();
+    mInitBootPropsFuture.callOnce([this] {
+        return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+    });
+
+    mInitBootPropsFuture.wait();
 }
 
 void SurfaceFlinger::run() {
     mScheduler->run();
 }
 
-sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure,
-                                          float requestedRefreshRate) {
-    // onTransact already checks for some permissions, but adding an additional check here.
-    // This is to ensure that only system and graphics can request to create a secure
+sp<IBinder> SurfaceFlinger::createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                                 const std::string& uniqueId,
+                                                 float requestedRefreshRate) {
+    // SurfaceComposerAIDL checks for some permissions, but adding an additional check here.
+    // This is to ensure that only root, system, and graphics can request to create a secure
     // display. Secure displays can show secure content so we add an additional restriction on it.
-    const int uid = IPCThreadState::self()->getCallingUid();
-    if (secure && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
+    const uid_t uid = IPCThreadState::self()->getCallingUid();
+    if (isSecure && uid != AID_ROOT && uid != AID_GRAPHICS && uid != AID_SYSTEM) {
         ALOGE("Only privileged processes can create a secure display");
         return nullptr;
     }
@@ -604,32 +598,34 @@
     Mutex::Autolock _l(mStateLock);
     // Display ID is assigned when virtual display is allocated by HWC.
     DisplayDeviceState state;
-    state.isSecure = secure;
+    state.isSecure = isSecure;
     // Set display as protected when marked as secure to ensure no behavior change
     // TODO (b/314820005): separate as a different arg when creating the display.
-    state.isProtected = secure;
+    state.isProtected = isSecure;
     state.displayName = displayName;
+    state.uniqueId = uniqueId;
     state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
     mCurrentState.displays.add(token, state);
     return token;
 }
 
-void SurfaceFlinger::destroyDisplay(const sp<IBinder>& displayToken) {
+status_t SurfaceFlinger::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
     Mutex::Autolock lock(mStateLock);
 
     const ssize_t index = mCurrentState.displays.indexOfKey(displayToken);
     if (index < 0) {
         ALOGE("%s: Invalid display token %p", __func__, displayToken.get());
-        return;
+        return NAME_NOT_FOUND;
     }
 
     const DisplayDeviceState& state = mCurrentState.displays.valueAt(index);
     if (state.physical) {
         ALOGE("%s: Invalid operation on physical display", __func__);
-        return;
+        return INVALID_OPERATION;
     }
     mCurrentState.displays.removeItemsAt(index);
     setTransactionFlags(eDisplayTransactionNeeded);
+    return NO_ERROR;
 }
 
 void SurfaceFlinger::enableHalVirtualDisplays(bool enable) {
@@ -723,13 +719,11 @@
     }
     mBootFinished = true;
     FlagManager::getMutableInstance().markBootCompleted();
-    if (mStartPropertySetThread->join() != NO_ERROR) {
-        ALOGE("Join StartPropertySetThread failed!");
-    }
 
-    if (mRenderEnginePrimeCacheFuture.valid()) {
-        mRenderEnginePrimeCacheFuture.get();
-    }
+    ::tracing_perfetto::registerWithPerfetto();
+    mInitBootPropsFuture.wait();
+    mRenderEnginePrimeCacheFuture.wait();
+
     const nsecs_t now = systemTime();
     const nsecs_t duration = now - mBootTime;
     ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
@@ -797,6 +791,8 @@
     char prop[PROPERTY_VALUE_MAX];
     property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
 
+    // TODO: b/293371537 - Once GraphiteVk is deemed relatively stable, log a warning that
+    // PROPERTY_DEBUG_RENDERENGINE_BACKEND is deprecated
     if (strcmp(prop, "skiagl") == 0) {
         builder.setThreaded(renderengine::RenderEngine::Threaded::NO)
                 .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::GL);
@@ -811,16 +807,55 @@
                 .setGraphicsApi(renderengine::RenderEngine::GraphicsApi::VK);
     } else {
         const auto kVulkan = renderengine::RenderEngine::GraphicsApi::VK;
-        const bool useVulkan = FlagManager::getInstance().vulkan_renderengine() &&
+// TODO: b/341728634 - Clean up conditional compilation.
+// Note: this guard in particular must check e.g.
+// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE directly (instead of calling e.g.
+// COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS(GRAPHITE_RENDERENGINE)) because that macro is undefined
+// in the libsurfaceflingerflags_test variant of com_android_graphics_surfaceflinger_flags.h, which
+// is used by layertracegenerator (which also needs SurfaceFlinger.cpp). :)
+#if COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_GRAPHITE_RENDERENGINE || \
+        COM_ANDROID_GRAPHICS_SURFACEFLINGER_FLAGS_FORCE_COMPILE_GRAPHITE_RENDERENGINE
+        const bool useGraphite = FlagManager::getInstance().graphite_renderengine() &&
                 renderengine::RenderEngine::canSupport(kVulkan);
+#else
+        const bool useGraphite = false;
+        if (FlagManager::getInstance().graphite_renderengine()) {
+            ALOGE("RenderEngine's Graphite Skia backend was requested with the "
+                  "debug.renderengine.graphite system property, but it is not compiled in this "
+                  "build! Falling back to Ganesh backend selection logic.");
+        }
+#endif
+        const bool useVulkan = useGraphite ||
+                (FlagManager::getInstance().vulkan_renderengine() &&
+                 renderengine::RenderEngine::canSupport(kVulkan));
+
+        builder.setSkiaBackend(useGraphite ? renderengine::RenderEngine::SkiaBackend::GRAPHITE
+                                           : renderengine::RenderEngine::SkiaBackend::GANESH);
         builder.setGraphicsApi(useVulkan ? kVulkan : renderengine::RenderEngine::GraphicsApi::GL);
     }
 }
 
-// Do not call property_set on main thread which will be blocked by init
-// Use StartPropertySetThread instead.
+/**
+ * Choose a suggested blurring algorithm if supportsBlur is true. By default Kawase will be
+ * suggested as it's faster than a full Gaussian blur and looks close enough.
+ */
+renderengine::RenderEngine::BlurAlgorithm chooseBlurAlgorithm(bool supportsBlur) {
+    if (!supportsBlur) {
+        return renderengine::RenderEngine::BlurAlgorithm::NONE;
+    }
+
+    auto const algorithm = base::GetProperty(PROPERTY_DEBUG_RENDERENGINE_BLUR_ALGORITHM, "");
+    if (algorithm == "gaussian") {
+        return renderengine::RenderEngine::BlurAlgorithm::GAUSSIAN;
+    } else if (algorithm == "kawase2") {
+        return renderengine::RenderEngine::BlurAlgorithm::KAWASE_DUAL_FILTER;
+    } else {
+        return renderengine::RenderEngine::BlurAlgorithm::KAWASE;
+    }
+}
+
 void SurfaceFlinger::init() FTL_FAKE_GUARD(kMainThreadContext) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGI(  "SurfaceFlinger's main thread ready to run. "
             "Initializing graphics H/W...");
     addTransactionReadyFilters();
@@ -834,7 +869,7 @@
                            .setImageCacheSize(maxFrameBufferAcquiredBuffers)
                            .setEnableProtectedContext(enable_protected_contents(false))
                            .setPrecacheToneMapperShaderOnly(false)
-                           .setSupportsBackgroundBlur(mSupportsBlur)
+                           .setBlurAlgorithm(chooseBlurAlgorithm(mSupportsBlur))
                            .setContextPriority(
                                    useContextPriority
                                            ? renderengine::RenderEngine::ContextPriority::REALTIME
@@ -851,10 +886,17 @@
     }
 
     mCompositionEngine->setTimeStats(mTimeStats);
+
     mCompositionEngine->setHwComposer(getFactory().createHWComposer(mHwcServiceName));
-    mCompositionEngine->getHwComposer().setCallback(*this);
+    auto& composer = mCompositionEngine->getHwComposer();
+    composer.setCallback(*this);
+    mDisplayModeController.setHwComposer(&composer);
+
     ClientCache::getInstance().setRenderEngine(&getRenderEngine());
 
+    mHasReliablePresentFences =
+            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
+
     enableLatchUnsignaledConfig = getLatchUnsignaledConfig();
 
     if (base::GetBoolProperty("debug.sf.enable_hwc_vds"s, false)) {
@@ -865,9 +907,11 @@
     LOG_ALWAYS_FATAL_IF(!configureLocked(),
                         "Initial display configuration failed: HWC did not hotplug");
 
+    mActiveDisplayId = getPrimaryDisplayIdLocked();
+
     // Commit primary display.
     sp<const DisplayDevice> display;
-    if (const auto indexOpt = mCurrentState.getDisplayIndex(getPrimaryDisplayIdLocked())) {
+    if (const auto indexOpt = mCurrentState.getDisplayIndex(mActiveDisplayId)) {
         const auto& displays = mCurrentState.displays;
 
         const auto& token = displays.keyAt(*indexOpt);
@@ -888,6 +932,20 @@
     // initializing the Scheduler after configureLocked, once decoupled from DisplayDevice.
     initScheduler(display);
 
+    // Start listening after creating the Scheduler, since the listener calls into it.
+    mDisplayModeController.setActiveModeListener(
+            display::DisplayModeController::ActiveModeListener::make(
+                    [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderRate) {
+                        // This callback cannot lock mStateLock, as some callers already lock it.
+                        // Instead, switch context to the main thread.
+                        static_cast<void>(mScheduler->schedule([=,
+                                                                this]() FTL_FAKE_GUARD(mStateLock) {
+                            if (const auto display = getDisplayDeviceLocked(displayId)) {
+                                display->updateRefreshRateOverlayRate(vsyncRate, renderRate);
+                            }
+                        }));
+                    }));
+
     mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
         auto snapshot = perfetto::protos::LayersSnapshotProto{};
         mScheduler
@@ -917,29 +975,67 @@
             ALOGW("Can't set SCHED_OTHER for primeCache");
         }
 
-        bool shouldPrimeUltraHDR =
-                base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
-        mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
+        mRenderEnginePrimeCacheFuture.callOnce([this] {
+            renderengine::PrimeCacheConfig config;
+            config.cacheHolePunchLayer =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.hole_punch"s, true);
+            config.cacheSolidLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.solid_layers"s, true);
+            config.cacheSolidDimmedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.solid_dimmed_layers"s, true);
+            config.cacheImageLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.image_layers"s, true);
+            config.cacheImageDimmedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.image_dimmed_layers"s, true);
+            config.cacheClippedLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.clipped_layers"s, true);
+            config.cacheShadowLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.shadow_layers"s, true);
+            config.cachePIPImageLayers =
+                    base::GetBoolProperty("debug.sf.prime_shader_cache.pip_image_layers"s, true);
+            config.cacheTransparentImageDimmedLayers = base::
+                    GetBoolProperty("debug.sf.prime_shader_cache.transparent_image_dimmed_layers"s,
+                                    true);
+            config.cacheClippedDimmedImageLayers = base::
+                    GetBoolProperty("debug.sf.prime_shader_cache.clipped_dimmed_image_layers"s,
+                                    true);
+            // ro.surface_flinger.prime_chader_cache.ultrahdr exists as a previous ro property
+            // which we maintain for backwards compatibility.
+            config.cacheUltraHDR =
+                    base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+            config.cacheEdgeExtension =
+                    base::GetBoolProperty("debug.sf.edge_extension_shader"s, true);
+            return getRenderEngine().primeCache(config);
+        });
 
         if (setSchedFifo(true) != NO_ERROR) {
             ALOGW("Can't set SCHED_FIFO after primeCache");
         }
     }
 
-    // Inform native graphics APIs whether the present timestamp is supported:
-
-    const bool presentFenceReliable =
-            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE);
-    mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);
-
-    if (mStartPropertySetThread->Start() != NO_ERROR) {
-        ALOGE("Run StartPropertySetThread failed!");
-    }
+    // Avoid blocking the main thread on `init` to set properties.
+    mInitBootPropsFuture.callOnce([this] {
+        return std::async(std::launch::async, &SurfaceFlinger::initBootProperties, this);
+    });
 
     initTransactionTraceWriter();
     ALOGV("Done initializing");
 }
 
+// During boot, offload `initBootProperties` to another thread. `property_set` depends on
+// `property_service`, which may be delayed by slow operations like `mount_all --late` in
+// the `init` process. See b/34499826 and b/63844978.
+void SurfaceFlinger::initBootProperties() {
+    property_set("service.sf.present_timestamp", mHasReliablePresentFences ? "1" : "0");
+
+    if (base::GetBoolProperty("debug.sf.boot_animation"s, true)) {
+        // Reset and (if needed) start BootAnimation.
+        property_set("service.bootanim.exit", "0");
+        property_set("service.bootanim.progress", "0");
+        property_set("ctl.start", "bootanim");
+    }
+}
+
 void SurfaceFlinger::initTransactionTraceWriter() {
     if (!mTransactionTracing) {
         return;
@@ -951,8 +1047,9 @@
                         ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
                         return;
                     }
-                    mTransactionTracing->flush();
+                    ALOGD("TransactionTraceWriter: writing file=%s", filename.c_str());
                     mTransactionTracing->writeToFile(filename);
+                    mTransactionTracing->flush();
                 };
                 if (std::this_thread::get_id() == mMainThreadId) {
                     writeFn();
@@ -979,18 +1076,6 @@
             static_cast<ui::ColorMode>(base::GetIntProperty("persist.sys.sf.color_mode"s, 0));
 }
 
-void SurfaceFlinger::startBootAnim() {
-    // Start boot animation service by setting a property mailbox
-    // if property setting thread is already running, Start() will be just a NOP
-    mStartPropertySetThread->Start();
-    // Wait until property was set
-    if (mStartPropertySetThread->join() != NO_ERROR) {
-        ALOGE("Join StartPropertySetThread failed!");
-    }
-}
-
-// ----------------------------------------------------------------------------
-
 status_t SurfaceFlinger::getSupportedFrameTimestamps(
         std::vector<FrameEvent>* outSupported) const {
     *outSupported = {
@@ -1004,9 +1089,7 @@
         FrameEvent::RELEASE,
     };
 
-    ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-
-    if (!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+    if (mHasReliablePresentFences) {
         outSupported->push_back(FrameEvent::DISPLAY_PRESENT);
     }
     return NO_ERROR;
@@ -1196,20 +1279,14 @@
         return BAD_VALUE;
     }
 
+    // TODO: b/277364366 - Require a display token from clients and remove fallback to pacesetter.
     std::optional<PhysicalDisplayId> displayIdOpt;
-    {
+    if (displayToken) {
         Mutex::Autolock lock(mStateLock);
-        if (displayToken) {
-            displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
-            if (!displayIdOpt) {
-                ALOGW("%s: Invalid physical display token %p", __func__, displayToken.get());
-                return NAME_NOT_FOUND;
-            }
-        } else {
-            // TODO (b/277364366): Clients should be updated to pass in the display they
-            // want, rather than us picking an arbitrary one (the active display, in this
-            // case).
-            displayIdOpt = mActiveDisplayId;
+        displayIdOpt = getPhysicalDisplayIdLocked(displayToken);
+        if (!displayIdOpt) {
+            ALOGW("%s: Invalid physical display token %p", __func__, displayToken.get());
+            return NAME_NOT_FOUND;
         }
     }
 
@@ -1228,21 +1305,21 @@
     const auto mode = desiredMode.mode;
     const auto displayId = mode.modePtr->getPhysicalDisplayId();
 
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
-
-    const auto display = getDisplayDeviceLocked(displayId);
-    if (!display) {
-        ALOGW("%s: display is no longer valid", __func__);
-        return;
-    }
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     const bool emitEvent = desiredMode.emitEvent;
 
-    switch (display->setDesiredMode(std::move(desiredMode))) {
-        case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
-            // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
-            mScheduler->setRenderRate(displayId,
-                                      display->refreshRateSelector().getActiveMode().fps);
+    using DesiredModeAction = display::DisplayModeController::DesiredModeAction;
+
+    switch (mDisplayModeController.setDesiredMode(displayId, std::move(desiredMode))) {
+        case DesiredModeAction::InitiateDisplayModeSwitch: {
+            const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId);
+            if (!selectorPtr) break;
+
+            const Fps renderRate = selectorPtr->getActiveMode().fps;
+
+            // DisplayModeController::setDesiredMode updated the render rate, so inform Scheduler.
+            mScheduler->setRenderRate(displayId, renderRate, true /* applyImmediately */);
 
             // Schedule a new frame to initiate the display mode switch.
             scheduleComposite(FrameHint::kNone);
@@ -1256,31 +1333,26 @@
             // VsyncController model is locked.
             mScheduler->modulateVsync(displayId, &VsyncModulator::onRefreshRateChangeInitiated);
 
-            if (displayId == mActiveDisplayId) {
-                mScheduler->updatePhaseConfiguration(mode.fps);
-            }
-
+            mScheduler->updatePhaseConfiguration(displayId, mode.fps);
             mScheduler->setModeChangePending(true);
             break;
-        case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
-            mScheduler->setRenderRate(displayId, mode.fps);
-
-            if (displayId == mActiveDisplayId) {
-                mScheduler->updatePhaseConfiguration(mode.fps);
-            }
+        }
+        case DesiredModeAction::InitiateRenderRateSwitch:
+            mScheduler->setRenderRate(displayId, mode.fps, /*applyImmediately*/ false);
+            mScheduler->updatePhaseConfiguration(displayId, mode.fps);
 
             if (emitEvent) {
-                dispatchDisplayModeChangeEvent(displayId, mode);
+                mScheduler->onDisplayModeChanged(displayId, mode);
             }
             break;
-        case DisplayDevice::DesiredModeAction::None:
+        case DesiredModeAction::None:
             break;
     }
 }
 
 status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
                                                    DisplayModeId modeId, Fps minFps, Fps maxFps) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -1329,11 +1401,12 @@
     return future.get();
 }
 
-void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) {
-    const auto displayId = display.getPhysicalId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+// TODO: b/241285876 - Restore thread safety analysis once mStateLock below is unconditional.
+[[clang::no_thread_safety_analysis]]
+void SurfaceFlinger::finalizeDisplayModeChange(PhysicalDisplayId displayId) {
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
-    const auto pendingModeOpt = display.getPendingMode();
+    const auto pendingModeOpt = mDisplayModeController.getPendingMode(displayId);
     if (!pendingModeOpt) {
         // There is no pending mode change. This can happen if the active
         // display changed and the mode change happened on a different display.
@@ -1342,8 +1415,12 @@
 
     const auto& activeMode = pendingModeOpt->mode;
 
-    if (display.getActiveMode().modePtr->getResolution() != activeMode.modePtr->getResolution()) {
-        auto& state = mCurrentState.displays.editValueFor(display.getDisplayToken());
+    if (const auto oldResolution =
+                mDisplayModeController.getActiveMode(displayId).modePtr->getResolution();
+        oldResolution != activeMode.modePtr->getResolution()) {
+        ConditionalLock lock(mStateLock, !FlagManager::getInstance().connected_display());
+
+        auto& state = mCurrentState.displays.editValueFor(getPhysicalDisplayTokenLocked(displayId));
         // We need to generate new sequenceId in order to recreate the display (and this
         // way the framebuffer).
         state.sequenceId = DisplayDeviceState{}.sequenceId;
@@ -1354,62 +1431,47 @@
         return;
     }
 
-    display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
-                               activeMode.fps);
+    mDisplayModeController.finalizeModeChange(displayId, activeMode.modePtr->getId(),
+                                              activeMode.modePtr->getVsyncRate(), activeMode.fps);
 
-    if (displayId == mActiveDisplayId) {
-        mScheduler->updatePhaseConfiguration(activeMode.fps);
-    }
+    mScheduler->updatePhaseConfiguration(displayId, activeMode.fps);
 
     if (pendingModeOpt->emitEvent) {
-        dispatchDisplayModeChangeEvent(displayId, activeMode);
+        mScheduler->onDisplayModeChanged(displayId, activeMode);
     }
 }
 
-void SurfaceFlinger::dropModeRequest(const sp<DisplayDevice>& display) {
-    display->clearDesiredMode();
-    if (display->getPhysicalId() == mActiveDisplayId) {
+void SurfaceFlinger::dropModeRequest(PhysicalDisplayId displayId) {
+    mDisplayModeController.clearDesiredMode(displayId);
+    if (displayId == mActiveDisplayId) {
         // TODO(b/255635711): Check for pending mode changes on other displays.
         mScheduler->setModeChangePending(false);
     }
 }
 
-void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) {
-    const auto activeModeOpt = display->getDesiredMode();
+void SurfaceFlinger::applyActiveMode(PhysicalDisplayId displayId) {
+    const auto activeModeOpt = mDisplayModeController.getDesiredMode(displayId);
     auto activeModePtr = activeModeOpt->mode.modePtr;
-    const auto displayId = activeModePtr->getPhysicalDisplayId();
     const auto renderFps = activeModeOpt->mode.fps;
 
-    dropModeRequest(display);
+    dropModeRequest(displayId);
 
     constexpr bool kAllowToEnable = true;
     mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
-    mScheduler->setRenderRate(displayId, renderFps);
 
-    if (displayId == mActiveDisplayId) {
-        mScheduler->updatePhaseConfiguration(renderFps);
-    }
+    mScheduler->setRenderRate(displayId, renderFps, /*applyImmediately*/ true);
+    mScheduler->updatePhaseConfiguration(displayId, renderFps);
 }
 
 void SurfaceFlinger::initiateDisplayModeChanges() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
-    std::optional<PhysicalDisplayId> displayToUpdateImmediately;
-
-    for (const auto& [id, physical] : mPhysicalDisplays) {
-        const auto display = getDisplayDeviceLocked(id);
-        if (!display) continue;
-
-        auto desiredModeOpt = display->getDesiredMode();
+    for (const auto& [displayId, physical] : mPhysicalDisplays) {
+        auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
         if (!desiredModeOpt) {
             continue;
         }
 
-        if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
-            dropModeRequest(display);
-            continue;
-        }
-
         const auto desiredModeId = desiredModeOpt->mode.modePtr->getId();
         const auto displayModePtrOpt = physical.snapshot().displayModes().get(desiredModeId);
 
@@ -1422,19 +1484,21 @@
         ALOGV("%s changing active mode to %d(%s) for display %s", __func__,
               ftl::to_underlying(desiredModeId),
               to_string(displayModePtrOpt->get()->getVsyncRate()).c_str(),
-              to_string(display->getId()).c_str());
+              to_string(displayId).c_str());
 
         if ((!FlagManager::getInstance().connected_display() || !desiredModeOpt->force) &&
-            display->getActiveMode() == desiredModeOpt->mode) {
-            applyActiveMode(display);
+            mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+            applyActiveMode(displayId);
             continue;
         }
 
+        const auto selectorPtr = mDisplayModeController.selectorPtrFor(displayId);
+
         // Desired active mode was set, it is different than the mode currently in use, however
         // allowed modes might have changed by the time we process the refresh.
         // Make sure the desired mode is still allowed
-        if (!display->refreshRateSelector().isModeAllowed(desiredModeOpt->mode)) {
-            dropModeRequest(display);
+        if (!selectorPtr->isModeAllowed(desiredModeOpt->mode)) {
+            dropModeRequest(displayId);
             continue;
         }
 
@@ -1444,30 +1508,25 @@
         constraints.seamlessRequired = false;
         hal::VsyncPeriodChangeTimeline outTimeline;
 
-        if (!display->initiateModeChange(std::move(*desiredModeOpt), constraints, outTimeline)) {
+        if (!mDisplayModeController.initiateModeChange(displayId, std::move(*desiredModeOpt),
+                                                       constraints, outTimeline)) {
             continue;
         }
 
-        display->refreshRateSelector().onModeChangeInitiated();
+        selectorPtr->onModeChangeInitiated();
         mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
 
         if (outTimeline.refreshRequired) {
             scheduleComposite(FrameHint::kNone);
         } else {
-            // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange`
-            // for all displays. This was only needed when the loop iterated over `mDisplays` rather
-            // than `mPhysicalDisplays`.
-            displayToUpdateImmediately = display->getPhysicalId();
-        }
-    }
+            // HWC has requested to apply the mode change immediately rather than on the next frame.
+            finalizeDisplayModeChange(displayId);
 
-    if (displayToUpdateImmediately) {
-        const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
-        finalizeDisplayModeChange(*display);
-
-        const auto desiredModeOpt = display->getDesiredMode();
-        if (desiredModeOpt && display->getActiveMode() == desiredModeOpt->mode) {
-            applyActiveMode(display);
+            const auto desiredModeOpt = mDisplayModeController.getDesiredMode(displayId);
+            if (desiredModeOpt &&
+                mDisplayModeController.getActiveMode(displayId) == desiredModeOpt->mode) {
+                applyActiveMode(displayId);
+            }
         }
     }
 }
@@ -1475,7 +1534,7 @@
 void SurfaceFlinger::disableExpensiveRendering() {
     const char* const whence = __func__;
     auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
-        ATRACE_NAME(whence);
+        SFTRACE_NAME(whence);
         if (mPowerAdvisor->isUsingExpensiveRendering()) {
             for (const auto& [_, display] : mDisplays) {
                 constexpr bool kDisable = false;
@@ -1840,19 +1899,6 @@
     return NO_ERROR;
 }
 
-status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
-    outLayers->clear();
-    auto future = mScheduler->schedule([=, this] {
-        const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            outLayers->push_back(layer->getLayerDebugInfo(display.get()));
-        });
-    });
-
-    future.wait();
-    return NO_ERROR;
-}
-
 status_t SurfaceFlinger::getCompositionPreference(
         Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
         Dataspace* outWideColorGamutDataspace,
@@ -2085,19 +2131,26 @@
 sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
         gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
         const sp<IBinder>& layerHandle) {
-    const auto cycle = vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
-            ? scheduler::Cycle::LastComposite
-            : scheduler::Cycle::Render;
+    const auto cycle = [&] {
+        if (FlagManager::getInstance().deprecate_vsync_sf()) {
+            ALOGW_IF(vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger,
+                "requested unsupported config eVsyncSourceSurfaceFlinger");
+            return scheduler::Cycle::Render;
+        }
 
+        return vsyncSource == gui::ISurfaceComposer::VsyncSource::eVsyncSourceSurfaceFlinger
+              ? scheduler::Cycle::LastComposite
+              : scheduler::Cycle::Render;
+    }();
     return mScheduler->createDisplayEventConnection(cycle, eventRegistration, layerHandle);
 }
 
-void SurfaceFlinger::scheduleCommit(FrameHint hint) {
+void SurfaceFlinger::scheduleCommit(FrameHint hint, Duration workDurationSlack) {
     if (hint == FrameHint::kActive) {
         mScheduler->resetIdleTimer();
     }
     mPowerAdvisor->notifyDisplayUpdateImminentAndCpuReset();
-    mScheduler->scheduleFrame();
+    mScheduler->scheduleFrame(workDurationSlack);
 }
 
 void SurfaceFlinger::scheduleComposite(FrameHint hint) {
@@ -2114,41 +2167,25 @@
     static_cast<void>(mScheduler->schedule([this] { sample(); }));
 }
 
-nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const {
-    if (const auto display = getDefaultDisplayDeviceLocked()) {
-        return display->getVsyncPeriodFromHWC();
-    }
-
-    return 0;
-}
-
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
     if (FlagManager::getInstance().connected_display() && timestamp < 0 &&
         vsyncPeriod.has_value()) {
-        // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
-        if (mIsHotplugErrViaNegVsync && vsyncPeriod.value() == ~0) {
-            const auto errorCode = static_cast<int32_t>(-timestamp);
-            ALOGD("%s: Hotplug error %d for display %" PRIu64, __func__, errorCode, hwcDisplayId);
-            mScheduler->dispatchHotplugError(errorCode);
-            return;
-        }
-
         if (mIsHdcpViaNegVsync && vsyncPeriod.value() == ~1) {
             const int32_t value = static_cast<int32_t>(-timestamp);
             // one byte is good enough to encode android.hardware.drm.HdcpLevel
             const int32_t maxLevel = (value >> 8) & 0xFF;
             const int32_t connectedLevel = value & 0xFF;
-            ALOGD("%s: HDCP levels changed (connected=%d, max=%d) for display %" PRIu64, __func__,
-                  connectedLevel, maxLevel, hwcDisplayId);
+            ALOGD("%s: HDCP levels changed (connected=%d, max=%d) for hwcDisplayId %" PRIu64,
+                  __func__, connectedLevel, maxLevel, hwcDisplayId);
             updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel);
             return;
         }
     }
 
-    ATRACE_NAME(vsyncPeriod
-                        ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
-                        : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
+    SFTRACE_NAME(vsyncPeriod
+                         ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
+                         : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
 
     Mutex::Autolock lock(mStateLock);
     if (const auto displayIdOpt = getHwComposer().onVsync(hwcDisplayId, timestamp)) {
@@ -2180,7 +2217,7 @@
     if (FlagManager::getInstance().hotplug2()) {
         // TODO(b/311403559): use enum type instead of int
         const auto errorCode = static_cast<int32_t>(event);
-        ALOGD("%s: Hotplug error %d for display %" PRIu64, __func__, errorCode, hwcDisplayId);
+        ALOGD("%s: Hotplug error %d for hwcDisplayId %" PRIu64, __func__, errorCode, hwcDisplayId);
         mScheduler->dispatchHotplugError(errorCode);
     }
 }
@@ -2206,24 +2243,39 @@
 }
 
 void SurfaceFlinger::onComposerHalVsyncIdle(hal::HWDisplayId) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mScheduler->forceNextResync();
 }
 
 void SurfaceFlinger::onRefreshRateChangedDebug(const RefreshRateChangedDebugData& data) {
-    ATRACE_CALL();
-    if (const auto displayId = getHwComposer().toPhysicalDisplayId(data.display); displayId) {
-        const char* const whence = __func__;
-        static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) {
-            const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
-                                                         ? data.refreshPeriodNanos
-                                                         : data.vsyncPeriodNanos);
-            ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
-            const auto display = getDisplayDeviceLocked(*displayId);
-            FTL_FAKE_GUARD(kMainThreadContext,
-                           display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
-                                                                 /* setByHwc */ true));
-        }));
+    SFTRACE_CALL();
+    const char* const whence = __func__;
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
+                                                   kMainThreadContext) {
+        if (const auto displayIdOpt = getHwComposer().toPhysicalDisplayId(data.display)) {
+            if (const auto display = getDisplayDeviceLocked(*displayIdOpt)) {
+                const Fps refreshRate = Fps::fromPeriodNsecs(
+                        getHwComposer().getComposer()->isVrrSupported() ? data.refreshPeriodNanos
+                                                                        : data.vsyncPeriodNanos);
+                SFTRACE_FORMAT("%s refresh rate = %d", whence, refreshRate.getIntValue());
+
+                const auto renderRate = mDisplayModeController.getActiveMode(*displayIdOpt).fps;
+                constexpr bool kSetByHwc = true;
+                display->updateRefreshRateOverlayRate(refreshRate, renderRate, kSetByHwc);
+            }
+        }
+    }));
+}
+
+void SurfaceFlinger::onComposerHalHdcpLevelsChanged(hal::HWDisplayId hwcDisplayId,
+                                                    const HdcpLevels& levels) {
+    if (FlagManager::getInstance().hdcp_level_hal()) {
+        // TODO(b/362270040): propagate enum constants
+        const int32_t maxLevel = static_cast<int32_t>(levels.maxLevel);
+        const int32_t connectedLevel = static_cast<int32_t>(levels.connectedLevel);
+        ALOGD("%s: HDCP levels changed (connected=%d, max=%d) for hwcDisplayId %" PRIu64, __func__,
+              connectedLevel, maxLevel, hwcDisplayId);
+        updateHdcpLevels(hwcDisplayId, connectedLevel, maxLevel);
     }
 }
 
@@ -2234,37 +2286,6 @@
     }
 }
 
-bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs,
-                                                bool flushTransactions,
-                                                bool& outTransactionsAreEmpty) {
-    ATRACE_CALL();
-    frontend::Update update;
-    if (flushTransactions) {
-        update = flushLifecycleUpdates();
-        if (mTransactionTracing) {
-            mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
-                                                          update, mFrontEndDisplayInfos,
-                                                          mFrontEndDisplayInfosChanged);
-        }
-    }
-
-    bool needsTraversal = false;
-    if (flushTransactions) {
-        needsTraversal |= commitMirrorDisplays(vsyncId);
-        needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates);
-        needsTraversal |= applyTransactions(update.transactions, vsyncId);
-    }
-    outTransactionsAreEmpty = !needsTraversal;
-    const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
-    if (shouldCommit) {
-        commitTransactionsLegacy();
-    }
-
-    bool mustComposite = latchBuffers() || shouldCommit;
-    updateLayerGeometry();
-    return mustComposite;
-}
-
 void SurfaceFlinger::updateLayerHistory(nsecs_t now) {
     for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
         using Changes = frontend::RequestedLayerState::Changes;
@@ -2336,10 +2357,10 @@
 bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs,
                                           bool flushTransactions, bool& outTransactionsAreEmpty) {
     using Changes = frontend::RequestedLayerState::Changes;
-    ATRACE_CALL();
+    SFTRACE_CALL();
     frontend::Update update;
     if (flushTransactions) {
-        ATRACE_NAME("TransactionHandler:flushTransactions");
+        SFTRACE_NAME("TransactionHandler:flushTransactions");
         // Locking:
         // 1. to prevent onHandleDestroyed from being called while the state lock is held,
         // we must keep a copy of the transactions (specifically the composer
@@ -2353,7 +2374,7 @@
         {
             // TODO(b/238781169) lockless queue this and keep order.
             std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-            update.layerCreatedStates = std::move(mCreatedLayers);
+            update.legacyLayers = std::move(mCreatedLayers);
             mCreatedLayers.clear();
             update.newLayers = std::move(mNewLayers);
             mNewLayers.clear();
@@ -2372,11 +2393,8 @@
         }
         mLayerLifecycleManager.applyTransactions(update.transactions);
         mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
-        for (auto& legacyLayer : update.layerCreatedStates) {
-            sp<Layer> layer = legacyLayer.layer.promote();
-            if (layer) {
-                mLegacyLayers[layer->sequence] = layer;
-            }
+        for (auto& legacyLayer : update.legacyLayers) {
+            mLegacyLayers[legacyLayer->sequence] = legacyLayer;
         }
         mLayerHierarchyBuilder.update(mLayerLifecycleManager);
     }
@@ -2391,10 +2409,12 @@
     mustComposite |= applyAndCommitDisplayTransactionStatesLocked(update.transactions);
 
     {
-        ATRACE_NAME("LayerSnapshotBuilder:update");
+        SFTRACE_NAME("LayerSnapshotBuilder:update");
         frontend::LayerSnapshotBuilder::Args
                 args{.root = mLayerHierarchyBuilder.getHierarchy(),
                      .layerLifecycleManager = mLayerLifecycleManager,
+                     .includeMetadata = mCompositionEngine->getFeatureFlags().test(
+                             compositionengine::Feature::kSnapshotLayerMetadata),
                      .displays = mFrontEndDisplayInfos,
                      .displayChanges = mFrontEndDisplayInfosChanged,
                      .globalShadowSettings = mDrawingState.globalShadowSettings,
@@ -2422,89 +2442,100 @@
         mUpdateAttachedChoreographer = true;
     }
     outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
-    mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
+    if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+        mustComposite |= mLayerLifecycleManager.getGlobalChanges().any(
+                frontend::RequestedLayerState::kMustComposite);
+    } else {
+        mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
+    }
 
     bool newDataLatched = false;
-    if (!mLegacyFrontEndEnabled) {
-        ATRACE_NAME("DisplayCallbackAndStatsUpdates");
-        mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
-        traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
-        const nsecs_t latchTime = systemTime();
-        bool unused = false;
+    SFTRACE_NAME("DisplayCallbackAndStatsUpdates");
+    mustComposite |= applyTransactionsLocked(update.transactions);
+    traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
+    const nsecs_t latchTime = systemTime();
+    bool unused = false;
 
-        for (auto& layer : mLayerLifecycleManager.getLayers()) {
-            if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
-                layer->bgColorLayer) {
-                sp<Layer> bgColorLayer = getFactory().createEffectLayer(
-                        LayerCreationArgs(this, nullptr, layer->name,
-                                          ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
-                                          std::make_optional(layer->id), true));
-                mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
-            }
-            const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
+    for (auto& layer : mLayerLifecycleManager.getLayers()) {
+        if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
+            layer->bgColorLayer) {
+            sp<Layer> bgColorLayer = getFactory().createEffectLayer(
+                    LayerCreationArgs(this, nullptr, layer->name,
+                                      ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
+                                      std::make_optional(layer->id), true));
+            mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
+        }
+        const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
 
-            auto it = mLegacyLayers.find(layer->id);
-            if (it == mLegacyLayers.end() &&
-                layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) {
-                // Layer handle was created and immediately destroyed. It was destroyed before it
-                // was added to the map.
-                continue;
-            }
-
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            layer->getDebugString().c_str());
-            if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
-                if (!it->second->hasBuffer()) {
-                    // The last latch time is used to classify a missed frame as buffer stuffing
-                    // instead of a missed frame. This is used to identify scenarios where we
-                    // could not latch a buffer or apply a transaction due to backpressure.
-                    // We only update the latch time for buffer less layers here, the latch time
-                    // is updated for buffer layers when the buffer is latched.
-                    it->second->updateLastLatchTime(latchTime);
-                }
-                continue;
-            }
-
-            const bool bgColorOnly =
-                    !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
-            if (willReleaseBufferOnLatch) {
-                mLayersWithBuffersRemoved.emplace(it->second);
-            }
-            it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
-            newDataLatched = true;
-
-            mLayersWithQueuedFrames.emplace(it->second);
-            mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
+        auto it = mLegacyLayers.find(layer->id);
+        if (it == mLegacyLayers.end() &&
+            layer->changes.test(frontend::RequestedLayerState::Changes::Destroyed)) {
+            // Layer handle was created and immediately destroyed. It was destroyed before it
+            // was added to the map.
+            continue;
         }
 
-        updateLayerHistory(latchTime);
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) ==
-                mLayersIdsWithQueuedFrames.end())
-                return;
+        LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                        "Couldnt find layer object for %s",
+                                        layer->getDebugString().c_str());
+        if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
+            if (!it->second->hasBuffer()) {
+                // The last latch time is used to classify a missed frame as buffer stuffing
+                // instead of a missed frame. This is used to identify scenarios where we
+                // could not latch a buffer or apply a transaction due to backpressure.
+                // We only update the latch time for buffer less layers here, the latch time
+                // is updated for buffer layers when the buffer is latched.
+                it->second->updateLastLatchTime(latchTime);
+            }
+            continue;
+        }
+
+        const bool bgColorOnly =
+                !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
+        if (willReleaseBufferOnLatch) {
+            mLayersWithBuffersRemoved.emplace(it->second);
+        }
+        it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
+        newDataLatched = true;
+
+        frontend::LayerSnapshot* snapshot = mLayerSnapshotBuilder.getSnapshot(it->second->sequence);
+        gui::GameMode gameMode = (snapshot) ? snapshot->gameMode : gui::GameMode::Unsupported;
+        mLayersWithQueuedFrames.emplace(it->second, gameMode);
+        mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
+    }
+
+    updateLayerHistory(latchTime);
+    mLayerSnapshotBuilder.forEachSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        // update output dirty region if we have a queued buffer that is visible or a snapshot
+        // recently became invisible
+        // TODO(b/360050020) investigate if we need to update dirty region when layer color changes
+        if ((snapshot.isVisible &&
+             (mLayersIdsWithQueuedFrames.find(snapshot.path.id) !=
+              mLayersIdsWithQueuedFrames.end())) ||
+            (!snapshot.isVisible && snapshot.changes.test(Changes::Visibility))) {
             Region visibleReg;
             visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
             invalidateLayerStack(snapshot.outputFilter, visibleReg);
-        });
-
-        for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
-            mLegacyLayers.erase(destroyedLayer->id);
         }
+    });
 
-        {
-            ATRACE_NAME("LLM:commitChanges");
-            mLayerLifecycleManager.commitChanges();
-        }
-
-        // enter boot animation on first buffer latch
-        if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
-            ALOGI("Enter boot animation");
-            mBootStage = BootStage::BOOTANIMATION;
-        }
+    for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+        mLegacyLayers.erase(destroyedLayer->id);
     }
+
+    {
+        SFTRACE_NAME("LayerLifecycleManager:commitChanges");
+        mLayerLifecycleManager.commitChanges();
+    }
+
+    // enter boot animation on first buffer latch
+    if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+        ALOGI("Enter boot animation");
+        mBootStage = BootStage::BOOTANIMATION;
+    }
+
     mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
-    if (mustComposite && !mLegacyFrontEndEnabled) {
+    if (mustComposite) {
         commitTransactions();
     }
 
@@ -2516,7 +2547,7 @@
     const scheduler::FrameTarget& pacesetterFrameTarget = *frameTargets.get(pacesetterId)->get();
 
     const VsyncId vsyncId = pacesetterFrameTarget.vsyncId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
     if (pacesetterFrameTarget.didMissFrame()) {
         mTimeStats->incrementMissedFrames();
@@ -2524,43 +2555,35 @@
 
     // If a mode set is pending and the fence hasn't fired yet, wait for the next commit.
     if (std::any_of(frameTargets.begin(), frameTargets.end(),
-                    [this](const auto& pair) FTL_FAKE_GUARD(mStateLock)
-                            FTL_FAKE_GUARD(kMainThreadContext) {
-                                if (!pair.second->isFramePending()) return false;
-
-                                if (const auto display = getDisplayDeviceLocked(pair.first)) {
-                                    return display->isModeSetPending();
-                                }
-
-                                return false;
-                            })) {
+                    [this](const auto& pair) FTL_FAKE_GUARD(kMainThreadContext) {
+                        const auto [displayId, target] = pair;
+                        return target->isFramePending() &&
+                                mDisplayModeController.isModeSetPending(displayId);
+                    })) {
         mScheduler->scheduleFrame();
         return false;
     }
 
     {
-        Mutex::Autolock lock(mStateLock);
+        ConditionalLock lock(mStateLock, FlagManager::getInstance().connected_display());
 
-        for (const auto [id, target] : frameTargets) {
-            // TODO(b/241285876): This is `nullptr` when the DisplayDevice is about to be removed in
-            // this commit, since the PhysicalDisplay has already been removed. Rather than checking
-            // for `nullptr` below, change Scheduler::onFrameSignal to filter out the FrameTarget of
-            // the removed display.
-            const auto display = getDisplayDeviceLocked(id);
-
-            if (display && display->isModeSetPending()) {
-                finalizeDisplayModeChange(*display);
+        for (const auto [displayId, _] : frameTargets) {
+            if (mDisplayModeController.isModeSetPending(displayId)) {
+                finalizeDisplayModeChange(displayId);
             }
         }
     }
 
-    if (pacesetterFrameTarget.isFramePending()) {
+    if (pacesetterFrameTarget.wouldBackpressureHwc()) {
         if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
             if (FlagManager::getInstance().vrr_config()) {
                 mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
                         pacesetterFrameTarget.expectedPresentTime());
             }
-            scheduleCommit(FrameHint::kNone);
+            const Duration slack = FlagManager::getInstance().allow_n_vsyncs_in_targeter()
+                    ? TimePoint::now() - pacesetterFrameTarget.frameBeginTime()
+                    : Duration::fromNs(0);
+            scheduleCommit(FrameHint::kNone, slack);
             return false;
         }
     }
@@ -2585,8 +2608,8 @@
         mPowerAdvisor->setFrameDelay(frameDelay);
         mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
 
-        const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
-        const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod();
+        const Period idealVsyncPeriod =
+                mDisplayModeController.getActiveMode(pacesetterId).fps.getPeriod();
         mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
     }
 
@@ -2606,18 +2629,18 @@
                                     mScheduler->getPacesetterRefreshRate());
 
         const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
-        bool transactionsAreEmpty;
-        if (mLegacyFrontEndEnabled) {
-            mustComposite |=
-                    updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
-                                               flushTransactions, transactionsAreEmpty);
-        }
-        if (mLayerLifecycleManagerEnabled) {
-            mustComposite |=
-                    updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
-                                         flushTransactions, transactionsAreEmpty);
-        }
+        bool transactionsAreEmpty = false;
+        mustComposite |= updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
+                                              flushTransactions, transactionsAreEmpty);
 
+        // Tell VsyncTracker that we are going to present this frame before scheduling
+        // setTransactionFlags which will schedule another SF frame. This was if the tracker
+        // needs to adjust the vsync timeline, it will be done before the next frame.
+        if (FlagManager::getInstance().vrr_config() && mustComposite) {
+            mScheduler->getVsyncSchedule()->getTracker().onFrameBegin(
+                pacesetterFrameTarget.expectedPresentTime(),
+                pacesetterFrameTarget.lastSignaledFrameTime());
+        }
         if (transactionFlushNeeded()) {
             setTransactionFlags(eTransactionFlushNeeded);
         }
@@ -2642,10 +2665,16 @@
         mUpdateAttachedChoreographer = false;
 
         Mutex::Autolock lock(mStateLock);
-        mScheduler->chooseRefreshRateForContent(mLayerLifecycleManagerEnabled
-                                                        ? &mLayerHierarchyBuilder.getHierarchy()
-                                                        : nullptr,
+        mScheduler->chooseRefreshRateForContent(&mLayerHierarchyBuilder.getHierarchy(),
                                                 updateAttachedChoreographer);
+
+        if (FlagManager::getInstance().connected_display()) {
+            initiateDisplayModeChanges();
+        }
+    }
+
+    if (!FlagManager::getInstance().connected_display()) {
+        ftl::FakeGuard guard(mStateLock);
         initiateDisplayModeChanges();
     }
 
@@ -2669,7 +2698,7 @@
             frameTargeters.get(pacesetterId)->get()->target();
 
     const VsyncId vsyncId = pacesetterTarget.vsyncId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
 
     compositionengine::CompositionRefreshArgs refreshArgs;
     refreshArgs.powerCallback = this;
@@ -2706,39 +2735,24 @@
 
     const bool updateTaskMetadata = mCompositionEngine->getFeatureFlags().test(
             compositionengine::Feature::kSnapshotLayerMetadata);
-    if (updateTaskMetadata && (mVisibleRegionsDirty || mLayerMetadataSnapshotNeeded)) {
-        updateLayerMetadataSnapshot();
-        mLayerMetadataSnapshotNeeded = false;
-    }
-
-    if (DOES_CONTAIN_BORDER) {
-        refreshArgs.borderInfoList.clear();
-        mDrawingState.traverse([&refreshArgs](Layer* layer) {
-            if (layer->isBorderEnabled()) {
-                compositionengine::BorderRenderInfo info;
-                info.width = layer->getBorderWidth();
-                info.color = layer->getBorderColor();
-                layer->traverse(LayerVector::StateSet::Drawing, [&info](Layer* ilayer) {
-                    info.layerIds.push_back(ilayer->getSequence());
-                });
-                refreshArgs.borderInfoList.emplace_back(std::move(info));
-            }
-        });
-    }
 
     refreshArgs.bufferIdsToUncache = std::move(mBufferIdsToUncache);
 
-    refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
-    for (auto layer : mLayersWithQueuedFrames) {
-        if (auto layerFE = layer->getCompositionEngineLayerFE())
-            refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& [layer, _] : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE(
+                        {static_cast<uint32_t>(layer->sequence)}))
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+        }
     }
 
     refreshArgs.outputColorSetting = mDisplayColorSetting;
     refreshArgs.forceOutputColorMode = mForceColorMode;
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
-    refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
+    refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) ||
+            mVisibleRegionsDirty || mDrawingState.colorMatrixChanged;
     refreshArgs.internalDisplayRotationFlags = getActiveDisplayRotationFlags();
 
     if (CC_UNLIKELY(mDrawingState.colorMatrixChanged)) {
@@ -2769,7 +2783,7 @@
     constexpr bool kCursorOnly = false;
     const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
 
-    if (mLayerLifecycleManagerEnabled && !mVisibleRegionsDirty) {
+    if (!mVisibleRegionsDirty) {
         for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
             auto compositionDisplay = display->getCompositionDisplay();
             if (!compositionDisplay->getState().isEnabled) continue;
@@ -2791,22 +2805,60 @@
         }
     }
 
-    mCompositionEngine->present(refreshArgs);
-    moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+    refreshArgs.refreshStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+    for (auto& [layer, layerFE] : layers) {
+        layer->onPreComposition(refreshArgs.refreshStartTime);
+    }
 
-    for (auto [layer, layerFE] : layers) {
-        CompositionResult compositionResult{layerFE->stealCompositionResult()};
-        layer->onPreComposition(compositionResult.refreshStartTime);
-        for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
-            Layer* clonedFrom = layer->getClonedFrom().get();
-            auto owningLayer = clonedFrom ? clonedFrom : layer;
-            owningLayer->onLayerDisplayed(std::move(releaseFence), layerStack);
+    if (FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            attachReleaseFenceFutureToLayer(layer, layerFE,
+                                            layerFE->mSnapshot->outputFilter.layerStack);
         }
-        if (compositionResult.lastClientCompositionFence) {
-            layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+
+        refreshArgs.layersWithQueuedFrames.reserve(mLayersWithQueuedFrames.size());
+        for (auto& [layer, _] : mLayersWithQueuedFrames) {
+            if (const auto& layerFE = layer->getCompositionEngineLayerFE(
+                        {static_cast<uint32_t>(layer->sequence)})) {
+                refreshArgs.layersWithQueuedFrames.push_back(layerFE);
+                // Some layers are not displayed and do not yet have a future release fence
+                if (layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::UNINITIALIZED ||
+                    layerFE->getReleaseFencePromiseStatus() ==
+                            LayerFE::ReleaseFencePromiseStatus::FULFILLED) {
+                    // layerStack is invalid because layer is not on a display
+                    attachReleaseFenceFutureToLayer(layer.get(), layerFE.get(),
+                                                    ui::INVALID_LAYER_STACK);
+                }
+            }
+        }
+
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto& [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
+        }
+
+    } else {
+        mCompositionEngine->present(refreshArgs);
+        moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+
+        for (auto [layer, layerFE] : layers) {
+            CompositionResult compositionResult{layerFE->stealCompositionResult()};
+            for (auto& [releaseFence, layerStack] : compositionResult.releaseFences) {
+                layer->onLayerDisplayed(std::move(releaseFence), layerStack);
+            }
+            if (compositionResult.lastClientCompositionFence) {
+                layer->setWasClientComposed(compositionResult.lastClientCompositionFence);
+            }
         }
     }
 
+    SFTRACE_NAME("postComposition");
     mTimeStats->recordFrameDuration(pacesetterTarget.frameBeginTime().ns(), systemTime());
 
     // Send a power hint after presentation is finished.
@@ -2814,6 +2866,9 @@
         // Now that the current frame has been presented above, PowerAdvisor needs the present time
         // of the previous frame (whose fence is signaled by now) to determine how long the HWC had
         // waited on that fence to retire before presenting.
+        // TODO(b/355238809) `presentFenceForPreviousFrame` might not always be signaled (e.g. on
+        // devices
+        //  where HWC does not block on the previous present fence). Revise this assumtion.
         const auto& previousPresentFence = pacesetterTarget.presentFenceForPreviousFrame();
 
         mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()),
@@ -2901,21 +2956,6 @@
     return resultsPerDisplay;
 }
 
-void SurfaceFlinger::updateLayerGeometry() {
-    ATRACE_CALL();
-
-    if (mVisibleRegionsDirty) {
-        computeLayerBounds();
-    }
-
-    for (auto& layer : mLayersPendingRefresh) {
-        Region visibleReg;
-        visibleReg.set(layer->getScreenBounds());
-        invalidateLayerStack(layer->getOutputFilter(), visibleReg);
-    }
-    mLayersPendingRefresh.clear();
-}
-
 bool SurfaceFlinger::isHdrLayer(const frontend::LayerSnapshot& snapshot) const {
     // Even though the camera layer may be using an HDR transfer function or otherwise be "HDR"
     // the device may need to avoid boosting the brightness as a result of these layers to
@@ -2986,7 +3026,7 @@
 void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId,
                                             const scheduler::FrameTargeters& frameTargeters,
                                             nsecs_t presentStartTime) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     ui::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<FenceTime>> presentFences;
     ui::PhysicalDisplayMap<PhysicalDisplayId, const sp<Fence>> gpuCompositionDoneFences;
@@ -3038,10 +3078,9 @@
     // but that should be okay since CompositorTiming has snapping logic.
     const TimePoint compositeTime =
             TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
-    const Duration presentLatency =
-            getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)
-            ? Duration::zero()
-            : mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime);
+    const Duration presentLatency = mHasReliablePresentFences
+            ? mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime)
+            : Duration::zero();
 
     const auto schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule->vsyncDeadlineAfter(presentTime);
@@ -3070,18 +3109,23 @@
             auto optDisplay = layerStackToDisplay.get(layerStack);
             if (optDisplay && !optDisplay->get()->isVirtual()) {
                 auto fence = getHwComposer().getPresentFence(optDisplay->get()->getPhysicalId());
-                layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
-                                        ui::INVALID_LAYER_STACK);
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    layer->prepareReleaseCallbacks(ftl::yield<FenceResult>(fence),
+                                                   ui::INVALID_LAYER_STACK);
+                } else {
+                    layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+                                            ui::INVALID_LAYER_STACK);
+                }
             }
         }
         layer->releasePendingBuffer(presentTime.ns());
     }
     mLayersWithBuffersRemoved.clear();
 
-    for (const auto& layer: mLayersWithQueuedFrames) {
+    for (const auto& [layer, gameMode] : mLayersWithQueuedFrames) {
         layer->onCompositionPresented(pacesetterDisplay.get(),
                                       pacesetterGpuCompositionDoneFenceTime,
-                                      pacesetterPresentFenceTime, compositorTiming);
+                                      pacesetterPresentFenceTime, compositorTiming, gameMode);
         layer->releasePendingBuffer(presentTime.ns());
     }
 
@@ -3142,27 +3186,20 @@
                         }
                     };
 
-            if (mLayerLifecycleManagerEnabled) {
-                mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                        [&, compositionDisplay = compositionDisplay](
-                                std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
-                            auto it = mLegacyLayers.find(snapshot->sequence);
-                            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                                            "Couldnt find layer object for %s",
-                                                            snapshot->getDebugString().c_str());
-                            auto& legacyLayer = it->second;
-                            sp<LayerFE> layerFe =
-                                    legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+            mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                    [&, compositionDisplay = compositionDisplay](
+                            std::unique_ptr<frontend::LayerSnapshot>& snapshot)
+                            FTL_FAKE_GUARD(kMainThreadContext) {
+                                auto it = mLegacyLayers.find(snapshot->sequence);
+                                LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                                "Couldnt find layer object for %s",
+                                                                snapshot->getDebugString().c_str());
+                                auto& legacyLayer = it->second;
+                                sp<LayerFE> layerFe =
+                                        legacyLayer->getCompositionEngineLayerFE(snapshot->path);
 
-                            updateInfoFn(compositionDisplay, *snapshot, layerFe);
-                        });
-            } else {
-                mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
-                    const auto layerFe = layer->getCompositionEngineLayerFE();
-                    const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
-                    updateInfoFn(compositionDisplay, snapshot, layerFe);
-                });
-            }
+                                updateInfoFn(compositionDisplay, *snapshot, layerFe);
+                            });
             listener->dispatchHdrLayerInfo(info);
         }
     }
@@ -3204,13 +3241,12 @@
 
     if (mNumTrustedPresentationListeners > 0) {
         // We avoid any reverse traversal upwards so this shouldn't be too expensive
-        traverseLegacyLayers([&](Layer* layer) {
+        traverseLegacyLayers([&](Layer* layer) FTL_FAKE_GUARD(kMainThreadContext) {
             if (!layer->hasTrustedPresentationListener()) {
                 return;
             }
-            const frontend::LayerSnapshot* snapshot = mLayerLifecycleManagerEnabled
-                    ? mLayerSnapshotBuilder.getSnapshot(layer->sequence)
-                    : layer->getLayerSnapshot();
+            const frontend::LayerSnapshot* snapshot =
+                    mLayerSnapshotBuilder.getSnapshot(layer->sequence);
             std::optional<const DisplayDevice*> displayOpt = std::nullopt;
             if (snapshot) {
                 displayOpt = layerStackToDisplay.get(snapshot->outputFilter.layerStack);
@@ -3222,48 +3258,18 @@
         });
     }
 
-    // Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the
+    // Even though SFTRACE_INT64 already checks if tracing is enabled, it doesn't prevent the
     // side-effect of getTotalSize(), so we check that again here
-    if (ATRACE_ENABLED()) {
+    if (SFTRACE_ENABLED()) {
         // getTotalSize returns the total number of buffers that were allocated by SurfaceFlinger
-        ATRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
+        SFTRACE_INT64("Total Buffer Size", GraphicBufferAllocator::get().getTotalSize());
     }
 
     logFrameStats(presentTime);
 }
 
-FloatRect SurfaceFlinger::getMaxDisplayBounds() {
-    const ui::Size maxSize = [this] {
-        ftl::FakeGuard guard(mStateLock);
-
-        // The LayerTraceGenerator tool runs without displays.
-        if (mDisplays.empty()) return ui::Size{5000, 5000};
-
-        return std::accumulate(mDisplays.begin(), mDisplays.end(), ui::kEmptySize,
-                               [](ui::Size size, const auto& pair) -> ui::Size {
-                                   const auto& display = pair.second;
-                                   return {std::max(size.getWidth(), display->getWidth()),
-                                           std::max(size.getHeight(), display->getHeight())};
-                               });
-    }();
-
-    // Ignore display bounds for now since they will be computed later. Use a large Rect bound
-    // to ensure it's bigger than an actual display will be.
-    const float xMax = maxSize.getWidth() * 10.f;
-    const float yMax = maxSize.getHeight() * 10.f;
-
-    return {-xMax, -yMax, xMax, yMax};
-}
-
-void SurfaceFlinger::computeLayerBounds() {
-    const FloatRect maxBounds = getMaxDisplayBounds();
-    for (const auto& layer : mDrawingState.layersSortedByZ) {
-        layer->computeBounds(maxBounds, ui::Transform(), 0.f /* shadowRadius */);
-    }
-}
-
 void SurfaceFlinger::commitTransactions() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     mDebugInTransaction = systemTime();
 
     // Here we're guaranteed that some transaction flags are set
@@ -3275,28 +3281,6 @@
     mDebugInTransaction = 0;
 }
 
-void SurfaceFlinger::commitTransactionsLegacy() {
-    ATRACE_CALL();
-
-    // Keep a copy of the drawing state (that is going to be overwritten
-    // by commitTransactionsLocked) outside of mStateLock so that the side
-    // effects of the State assignment don't happen with mStateLock held,
-    // which can cause deadlocks.
-    State drawingState(mDrawingState);
-
-    Mutex::Autolock lock(mStateLock);
-    mDebugInTransaction = systemTime();
-
-    // Here we're guaranteed that some transaction flags are set
-    // so we can call commitTransactionsLocked unconditionally.
-    // We clear the flags with mStateLock held to guarantee that
-    // mCurrentState won't change until the transaction is committed.
-    mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit);
-    commitTransactionsLocked(clearTransactionFlags(eTransactionMask));
-
-    mDebugInTransaction = 0;
-}
-
 std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
         PhysicalDisplayId displayId) const {
     std::vector<HWComposer::HWCDisplayMode> hwcModes;
@@ -3461,12 +3445,45 @@
     for (const auto [hwcDisplayId, connection] : events) {
         if (auto info = getHwComposer().onHotplug(hwcDisplayId, connection)) {
             const auto displayId = info->id;
-            const bool connected = connection == hal::Connection::CONNECTED;
+            const ftl::Concat displayString("display ", displayId.value, "(HAL ID ", hwcDisplayId,
+                                            ')');
 
-            if (const char* const log =
-                        processHotplug(displayId, hwcDisplayId, connected, std::move(*info))) {
-                ALOGI("%s display %s (HAL ID %" PRIu64 ")", log, to_string(displayId).c_str(),
-                      hwcDisplayId);
+            if (connection == hal::Connection::CONNECTED) {
+                const auto activeModeIdOpt =
+                        processHotplugConnect(displayId, hwcDisplayId, std::move(*info),
+                                              displayString.c_str());
+                if (!activeModeIdOpt) {
+                    if (FlagManager::getInstance().hotplug2()) {
+                        mScheduler->dispatchHotplugError(
+                                static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
+                    }
+                    getHwComposer().disconnectDisplay(displayId);
+                    continue;
+                }
+
+                const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
+                        getKernelIdleTimerProperties(displayId);
+
+                using Config = scheduler::RefreshRateSelector::Config;
+                const Config config =
+                        {.enableFrameRateOverride = sysprop::enable_frame_rate_override(true)
+                                 ? Config::FrameRateOverride::Enabled
+                                 : Config::FrameRateOverride::Disabled,
+                         .frameRateMultipleThreshold =
+                                 base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0),
+                         .legacyIdleTimerTimeout = idleTimerTimeoutMs,
+                         .kernelIdleTimerController = kernelIdleTimerController};
+
+                const auto snapshotOpt =
+                        mPhysicalDisplays.get(displayId).transform(&PhysicalDisplay::snapshotRef);
+                LOG_ALWAYS_FATAL_IF(!snapshotOpt);
+
+                mDisplayModeController.registerDisplay(*snapshotOpt, *activeModeIdOpt, config);
+            } else {
+                // Unregister before destroying the DisplaySnapshot below.
+                mDisplayModeController.unregisterDisplay(displayId);
+
+                processHotplugDisconnect(displayId, displayString.c_str());
             }
         }
     }
@@ -3474,36 +3491,20 @@
     return !events.empty();
 }
 
-const char* SurfaceFlinger::processHotplug(PhysicalDisplayId displayId,
-                                           hal::HWDisplayId hwcDisplayId, bool connected,
-                                           DisplayIdentificationInfo&& info) {
-    const auto displayOpt = mPhysicalDisplays.get(displayId);
-    if (!connected) {
-        LOG_ALWAYS_FATAL_IF(!displayOpt);
-        const auto& display = displayOpt->get();
-
-        if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
-            mCurrentState.displays.removeItemsAt(index);
-        }
-
-        mPhysicalDisplays.erase(displayId);
-        return "Disconnecting";
-    }
-
+std::optional<DisplayModeId> SurfaceFlinger::processHotplugConnect(PhysicalDisplayId displayId,
+                                                                   hal::HWDisplayId hwcDisplayId,
+                                                                   DisplayIdentificationInfo&& info,
+                                                                   const char* displayString) {
     auto [displayModes, activeMode] = loadDisplayModes(displayId);
     if (!activeMode) {
-        ALOGE("Failed to hotplug display %s", to_string(displayId).c_str());
-        if (FlagManager::getInstance().hotplug2()) {
-            mScheduler->dispatchHotplugError(
-                    static_cast<int32_t>(DisplayHotplugEvent::ERROR_UNKNOWN));
-        }
-        getHwComposer().disconnectDisplay(displayId);
-        return nullptr;
+        ALOGE("Failed to hotplug %s", displayString);
+        return std::nullopt;
     }
 
+    const DisplayModeId activeModeId = activeMode->getId();
     ui::ColorModes colorModes = getHwComposer().getColorModes(displayId);
 
-    if (displayOpt) {
+    if (const auto displayOpt = mPhysicalDisplays.get(displayId)) {
         const auto& display = displayOpt->get();
         const auto& snapshot = display.snapshot();
 
@@ -3522,7 +3523,8 @@
         auto& state = mCurrentState.displays.editValueFor(it->second.token());
         state.sequenceId = DisplayDeviceState{}.sequenceId; // Generate new sequenceId.
         state.physical->activeMode = std::move(activeMode);
-        return "Reconnecting";
+        ALOGI("Reconnecting %s", displayString);
+        return activeModeId;
     }
 
     const sp<IBinder> token = sp<BBinder>::make();
@@ -3537,22 +3539,33 @@
     state.physical = {.id = displayId,
                       .hwcDisplayId = hwcDisplayId,
                       .activeMode = std::move(activeMode)};
-    state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
+    if (mIsHdcpViaNegVsync) {
+        state.isSecure = connectionType == ui::DisplayConnectionType::Internal;
+    } else {
+        // TODO(b/349703362): Remove this when HDCP aidl API becomes ready
+        state.isSecure = true; // All physical displays are currently considered secure.
+    }
     state.isProtected = true;
     state.displayName = std::move(info.name);
 
     mCurrentState.displays.add(token, state);
-    return "Connecting";
+    ALOGI("Connecting %s", displayString);
+    return activeModeId;
 }
 
-void SurfaceFlinger::dispatchDisplayModeChangeEvent(PhysicalDisplayId displayId,
-                                                    const scheduler::FrameRateMode& mode) {
-    // TODO(b/255635821): Merge code paths and move to Scheduler.
-    const auto onDisplayModeChanged = displayId == mActiveDisplayId
-            ? &scheduler::Scheduler::onPrimaryDisplayModeChanged
-            : &scheduler::Scheduler::onNonPrimaryDisplayModeChanged;
+void SurfaceFlinger::processHotplugDisconnect(PhysicalDisplayId displayId,
+                                              const char* displayString) {
+    ALOGI("Disconnecting %s", displayString);
 
-    ((*mScheduler).*onDisplayModeChanged)(scheduler::Cycle::Render, mode);
+    const auto displayOpt = mPhysicalDisplays.get(displayId);
+    LOG_ALWAYS_FATAL_IF(!displayOpt);
+    const auto& display = displayOpt->get();
+
+    if (const ssize_t index = mCurrentState.displays.indexOfKey(display.token()); index >= 0) {
+        mCurrentState.displays.removeItemsAt(index);
+    }
+
+    mPhysicalDisplays.erase(displayId);
 }
 
 sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
@@ -3570,43 +3583,21 @@
     creationArgs.hasWideColorGamut = false;
     creationArgs.supportedPerFrameMetadata = 0;
 
-    if (const auto& physical = state.physical) {
-        creationArgs.activeModeId = physical->activeMode->getId();
-        const auto [kernelIdleTimerController, idleTimerTimeoutMs] =
-                getKernelIdleTimerProperties(compositionDisplay->getId());
+    if (const auto physicalIdOpt = PhysicalDisplayId::tryCast(compositionDisplay->getId())) {
+        const auto physicalId = *physicalIdOpt;
 
-        using Config = scheduler::RefreshRateSelector::Config;
-        const auto enableFrameRateOverride = sysprop::enable_frame_rate_override(true)
-                ? Config::FrameRateOverride::Enabled
-                : Config::FrameRateOverride::Disabled;
-        const Config config =
-                {.enableFrameRateOverride = enableFrameRateOverride,
-                 .frameRateMultipleThreshold =
-                         base::GetIntProperty("debug.sf.frame_rate_multiple_threshold"s, 0),
-                 .idleTimerTimeout = idleTimerTimeoutMs,
-                 .kernelIdleTimerController = kernelIdleTimerController};
-
+        creationArgs.isPrimary = physicalId == getPrimaryDisplayIdLocked();
         creationArgs.refreshRateSelector =
-                mPhysicalDisplays.get(physical->id)
-                        .transform(&PhysicalDisplay::snapshotRef)
-                        .transform([&](const display::DisplaySnapshot& snapshot) {
-                            return std::make_shared<
-                                    scheduler::RefreshRateSelector>(snapshot.displayModes(),
-                                                                    creationArgs.activeModeId,
-                                                                    config);
-                        })
-                        .value_or(nullptr);
+                FTL_FAKE_GUARD(kMainThreadContext,
+                               mDisplayModeController.selectorPtrFor(physicalId));
 
-        creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked();
-
-        mPhysicalDisplays.get(physical->id)
+        mPhysicalDisplays.get(physicalId)
                 .transform(&PhysicalDisplay::snapshotRef)
                 .transform(ftl::unit_fn([&](const display::DisplaySnapshot& snapshot) {
                     for (const auto mode : snapshot.colorModes()) {
                         creationArgs.hasWideColorGamut |= ui::isWideColorMode(mode);
                         creationArgs.hwcColorModes
-                                .emplace(mode,
-                                         getHwComposer().getRenderIntents(physical->id, mode));
+                                .emplace(mode, getHwComposer().getRenderIntents(physicalId, mode));
                     }
                 }));
     }
@@ -3651,7 +3642,8 @@
 
     if (const auto& physical = state.physical) {
         const auto& mode = *physical->activeMode;
-        display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate());
+        mDisplayModeController.setActiveMode(physical->id, mode.getId(), mode.getVsyncRate(),
+                                             mode.getPeakFps());
     }
 
     display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
@@ -3721,11 +3713,20 @@
                  state.surface.get());
         const auto displayId = PhysicalDisplayId::tryCast(compositionDisplay->getId());
         LOG_FATAL_IF(!displayId);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        const auto frameBufferSurface =
+                sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqProducer, bqConsumer,
+                                             state.physical->activeMode->getResolution(),
+                                             ui::Size(maxGraphicsWidth, maxGraphicsHeight));
+        displaySurface = frameBufferSurface;
+        producer = frameBufferSurface->getSurface()->getIGraphicBufferProducer();
+#else
         displaySurface =
                 sp<FramebufferSurface>::make(getHwComposer(), *displayId, bqConsumer,
                                              state.physical->activeMode->getResolution(),
                                              ui::Size(maxGraphicsWidth, maxGraphicsHeight));
         producer = bqProducer;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     LOG_FATAL_IF(!displaySurface);
@@ -3737,7 +3738,8 @@
         ftl::FakeGuard guard(kMainThreadContext);
 
         // For hotplug reconnect, renew the registration since display modes have been reloaded.
-        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+        mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                    mActiveDisplayId);
     }
 
     if (display->isVirtual()) {
@@ -3776,7 +3778,7 @@
         if (display->isVirtual()) {
             releaseVirtualDisplay(display->getVirtualId());
         } else {
-            mScheduler->unregisterDisplay(display->getPhysicalId());
+            mScheduler->unregisterDisplay(display->getPhysicalId(), mActiveDisplayId);
         }
     }
 
@@ -3817,18 +3819,20 @@
         mDisplays.erase(displayToken);
 
         if (const auto& physical = currentState.physical) {
-            getHwComposer().allocatePhysicalDisplay(physical->hwcDisplayId, physical->id);
+            getHwComposer().allocatePhysicalDisplay(physical->hwcDisplayId, physical->id,
+                                                    /*physicalSize=*/std::nullopt);
         }
 
         processDisplayAdded(displayToken, currentState);
 
         if (currentState.physical) {
             const auto display = getDisplayDeviceLocked(displayToken);
-            setPowerModeInternal(display, hal::PowerMode::ON);
+            if (!mSkipPowerOnForQuiescent) {
+                setPowerModeInternal(display, hal::PowerMode::ON);
+            }
 
-            // TODO(b/175678251) Call a listener instead.
-            if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
-                mScheduler->resetPhaseConfiguration(display->getActiveMode().fps);
+            if (display->getPhysicalId() == mActiveDisplayId) {
+                onActiveDisplayChangedLocked(nullptr, *display);
             }
         }
         return;
@@ -3912,14 +3916,6 @@
     // Commit display transactions.
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
     mFrontEndDisplayInfosChanged = displayTransactionNeeded;
-    if (displayTransactionNeeded && !mLayerLifecycleManagerEnabled) {
-        processDisplayChangesLocked();
-        mFrontEndDisplayInfos.clear();
-        for (const auto& [_, display] : mDisplays) {
-            mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo());
-        }
-    }
-    mForceTransactionDisplayChange = displayTransactionNeeded;
 
     if (mSomeChildrenChanged) {
         mVisibleRegionsDirty = true;
@@ -3927,63 +3923,6 @@
         mUpdateInputInfo = true;
     }
 
-    // Update transform hint.
-    if (transactionFlags & (eTransformHintUpdateNeeded | eDisplayTransactionNeeded)) {
-        // Layers and/or displays have changed, so update the transform hint for each layer.
-        //
-        // NOTE: we do this here, rather than when presenting the display so that
-        // the hint is set before we acquire a buffer from the surface texture.
-        //
-        // NOTE: layer transactions have taken place already, so we use their
-        // drawing state. However, SurfaceFlinger's own transaction has not
-        // happened yet, so we must use the current state layer list
-        // (soon to become the drawing state list).
-        //
-        sp<const DisplayDevice> hintDisplay;
-        ui::LayerStack layerStack;
-
-        mCurrentState.traverse([&](Layer* layer) REQUIRES(mStateLock) {
-            // NOTE: we rely on the fact that layers are sorted by
-            // layerStack first (so we don't have to traverse the list
-            // of displays for every layer).
-            if (const auto filter = layer->getOutputFilter(); layerStack != filter.layerStack) {
-                layerStack = filter.layerStack;
-                hintDisplay = nullptr;
-
-                // Find the display that includes the layer.
-                for (const auto& [token, display] : mDisplays) {
-                    if (!display->getCompositionDisplay()->includesLayer(filter)) {
-                        continue;
-                    }
-
-                    // Pick the primary display if another display mirrors the layer.
-                    if (hintDisplay) {
-                        hintDisplay = nullptr;
-                        break;
-                    }
-
-                    hintDisplay = display;
-                }
-            }
-
-            if (!hintDisplay) {
-                // NOTE: TEMPORARY FIX ONLY. Real fix should cause layers to
-                // redraw after transform hint changes. See bug 8508397.
-                // could be null when this layer is using a layerStack
-                // that is not visible on any display. Also can occur at
-                // screen off/on times.
-                // U Update: Don't provide stale hints to the clients. For
-                // special cases where we want the app to draw its
-                // first frame before the display is available, we rely
-                // on WMS and DMS to provide the right information
-                // so the client can calculate the hint.
-                layer->skipReportingTransformHint();
-            } else {
-                layer->updateTransformHint(hintDisplay->getTransformHint());
-            }
-        });
-    }
-
     if (mLayersAdded) {
         mLayersAdded = false;
         // Layers have been added.
@@ -3997,14 +3936,6 @@
         mLayersRemoved = false;
         mVisibleRegionsDirty = true;
         mUpdateInputInfo = true;
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            if (mLayersPendingRemoval.indexOf(sp<Layer>::fromExisting(layer)) >= 0) {
-                // this layer is not visible anymore
-                Region visibleReg;
-                visibleReg.set(layer->getScreenBounds());
-                invalidateLayerStack(layer->getOutputFilter(), visibleReg);
-            }
-        });
     }
 
     if (transactionFlags & eInputInfoUpdateNeeded) {
@@ -4018,7 +3949,7 @@
     if (!mInputFlinger || (!mUpdateInputInfo && mInputWindowCommands.empty())) {
         return;
     }
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::vector<WindowInfo> windowInfos;
     std::vector<DisplayInfo> displayInfos;
@@ -4048,7 +3979,7 @@
                                                               std::move(mInputWindowCommands),
                                                       inputFlinger = mInputFlinger, this,
                                                       visibleWindowsChanged, vsyncId, frameTime]() {
-        ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
+        SFTRACE_NAME("BackgroundExecutor::updateInputFlinger");
         if (updateWindowInfo) {
             mWindowInfosListenerInvoker
                     ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos),
@@ -4111,23 +4042,10 @@
     outWindowInfos.reserve(sNumWindowInfos);
     sNumWindowInfos = 0;
 
-    if (mLayerLifecycleManagerEnabled) {
-        mLayerSnapshotBuilder.forEachInputSnapshot(
-                [&outWindowInfos](const frontend::LayerSnapshot& snapshot) {
-                    outWindowInfos.push_back(snapshot.inputInfo);
-                });
-    } else {
-        mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
-            if (!layer->needsInputInfo()) return;
-            const auto opt =
-                    mFrontEndDisplayInfos.get(layer->getLayerStack())
-                            .transform([](const frontend::DisplayInfo& info) {
-                                return Layer::InputDisplayArgs{&info.transform, info.isSecure};
-                            });
-
-            outWindowInfos.push_back(layer->fillInputInfo(opt.value_or(Layer::InputDisplayArgs{})));
-        });
-    }
+    mLayerSnapshotBuilder.forEachInputSnapshot(
+            [&outWindowInfos](const frontend::LayerSnapshot& snapshot) {
+                outWindowInfos.push_back(snapshot.inputInfo);
+            });
 
     sNumWindowInfos = outWindowInfos.size();
 
@@ -4161,7 +4079,7 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     // If this is called from the main thread mStateLock must be locked before
     // Currently the only way to call this function from the main thread is from
@@ -4177,12 +4095,6 @@
 
         if (!display) continue;
 
-        if (ftl::FakeGuard guard(kMainThreadContext);
-            !shouldApplyRefreshRateSelectorPolicy(*display)) {
-            ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
-            continue;
-        }
-
         if (display->refreshRateSelector().isModeAllowed(request.mode)) {
             setDesiredMode(std::move(request));
         } else {
@@ -4192,25 +4104,14 @@
     }
 }
 
-void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
-    PhysicalDisplayId displayId = [&]() {
-        ConditionalLock lock(mStateLock, std::this_thread::get_id() != mMainThreadId);
-        return getDefaultDisplayDeviceLocked()->getPhysicalId();
-    }();
-
-    mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
-}
-
 void SurfaceFlinger::notifyCpuLoadUp() {
     mPowerAdvisor->notifyCpuLoadUp();
 }
 
 void SurfaceFlinger::onChoreographerAttached() {
-    ATRACE_CALL();
-    if (mLayerLifecycleManagerEnabled) {
-        mUpdateAttachedChoreographer = true;
-        scheduleCommit(FrameHint::kNone);
-    }
+    SFTRACE_CALL();
+    mUpdateAttachedChoreographer = true;
+    scheduleCommit(FrameHint::kNone);
 }
 
 void SurfaceFlinger::onExpectedPresentTimePosted(TimePoint expectedPresentTime,
@@ -4335,6 +4236,8 @@
             if (data.hintStatus.compare_exchange_strong(scheduleHintOnTx,
                                                         NotifyExpectedPresentHintStatus::Sent)) {
                 sendHint();
+                constexpr bool kAllowToEnable = true;
+                mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable);
             }
         }));
     }
@@ -4354,6 +4257,12 @@
     scheduleNotifyExpectedPresentHint(displayId);
 }
 
+void SurfaceFlinger::onCommitNotComposited() {
+    if (FlagManager::getInstance().commit_not_composited()) {
+        mFrameTimeline->onCommitNotComposited();
+    }
+}
+
 void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
     using namespace scheduler;
 
@@ -4377,7 +4286,7 @@
         features |= Feature::kTracePredictedVsync;
     }
     if (!base::GetBoolProperty("debug.sf.vsync_reactor_ignore_present_fences"s, false) &&
-        !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
+        mHasReliablePresentFences) {
         features |= Feature::kPresentFences;
     }
     if (display->refreshRateSelector().kernelIdleTimerController()) {
@@ -4396,9 +4305,11 @@
                                              getFactory(), activeRefreshRate, *mTimeStats);
 
     // The pacesetter must be registered before EventThread creation below.
-    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+    mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector(),
+                                mActiveDisplayId);
     if (FlagManager::getInstance().vrr_config()) {
-        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
+        mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps,
+                                  /*applyImmediately*/ true);
     }
 
     const auto configs = mScheduler->getVsyncConfiguration().getCurrentConfigs();
@@ -4426,75 +4337,9 @@
 }
 
 void SurfaceFlinger::doCommitTransactions() {
-    ATRACE_CALL();
-
-    if (!mLayersPendingRemoval.isEmpty()) {
-        // Notify removed layers now that they can't be drawn from
-        for (const auto& l : mLayersPendingRemoval) {
-            // Ensure any buffers set to display on any children are released.
-            if (l->isRemovedFromCurrentState()) {
-                l->latchAndReleaseBuffer();
-            }
-
-            // If a layer has a parent, we allow it to out-live it's handle
-            // with the idea that the parent holds a reference and will eventually
-            // be cleaned up. However no one cleans up the top-level so we do so
-            // here.
-            if (l->isAtRoot()) {
-                l->setIsAtRoot(false);
-                mCurrentState.layersSortedByZ.remove(l);
-            }
-
-            // If the layer has been removed and has no parent, then it will not be reachable
-            // when traversing layers on screen. Add the layer to the offscreenLayers set to
-            // ensure we can copy its current to drawing state.
-            if (!l->getParent()) {
-                mOffscreenLayers.emplace(l.get());
-            }
-        }
-        mLayersPendingRemoval.clear();
-    }
-
+    SFTRACE_CALL();
     mDrawingState = mCurrentState;
     mCurrentState.colorMatrixChanged = false;
-
-    if (mVisibleRegionsDirty) {
-        for (const auto& rootLayer : mDrawingState.layersSortedByZ) {
-            rootLayer->commitChildList();
-        }
-    }
-
-    commitOffscreenLayers();
-    if (mLayerMirrorRoots.size() > 0) {
-        std::deque<Layer*> pendingUpdates;
-        pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(),
-                              mLayerMirrorRoots.end());
-        std::vector<Layer*> needsUpdating;
-        for (Layer* cloneRoot : mLayerMirrorRoots) {
-            pendingUpdates.pop_front();
-            if (cloneRoot->isRemovedFromCurrentState()) {
-                continue;
-            }
-            if (cloneRoot->updateMirrorInfo(pendingUpdates)) {
-            } else {
-                needsUpdating.push_back(cloneRoot);
-            }
-        }
-        for (Layer* cloneRoot : needsUpdating) {
-            cloneRoot->updateMirrorInfo({});
-        }
-    }
-}
-
-void SurfaceFlinger::commitOffscreenLayers() {
-    for (Layer* offscreenLayer : mOffscreenLayers) {
-        offscreenLayer->traverse(LayerVector::StateSet::Drawing, [](Layer* layer) {
-            if (layer->clearTransactionFlags(eTransactionNeeded)) {
-                layer->doTransaction(0);
-                layer->commitChildList();
-            }
-        });
-    }
 }
 
 void SurfaceFlinger::invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty) {
@@ -4506,160 +4351,9 @@
     }
 }
 
-bool SurfaceFlinger::latchBuffers() {
-    ATRACE_CALL();
-
-    const nsecs_t latchTime = systemTime();
-
-    bool visibleRegions = false;
-    bool frameQueued = false;
-    bool newDataLatched = false;
-
-    // Store the set of layers that need updates. This set must not change as
-    // buffers are being latched, as this could result in a deadlock.
-    // Example: Two producers share the same command stream and:
-    // 1.) Layer 0 is latched
-    // 2.) Layer 0 gets a new frame
-    // 2.) Layer 1 gets a new frame
-    // 3.) Layer 1 is latched.
-    // Display is now waiting on Layer 1's frame, which is behind layer 0's
-    // second frame. But layer 0's second frame could be waiting on display.
-    mDrawingState.traverse([&](Layer* layer) {
-        if (layer->clearTransactionFlags(eTransactionNeeded) || mForceTransactionDisplayChange) {
-            const uint32_t flags = layer->doTransaction(0);
-            if (flags & Layer::eVisibleRegion) {
-                mVisibleRegionsDirty = true;
-            }
-        }
-
-        if (layer->hasReadyFrame() || layer->willReleaseBufferOnLatch()) {
-            frameQueued = true;
-            mLayersWithQueuedFrames.emplace(sp<Layer>::fromExisting(layer));
-        } else {
-            layer->useEmptyDamage();
-            if (!layer->hasBuffer()) {
-                // The last latch time is used to classify a missed frame as buffer stuffing
-                // instead of a missed frame. This is used to identify scenarios where we
-                // could not latch a buffer or apply a transaction due to backpressure.
-                // We only update the latch time for buffer less layers here, the latch time
-                // is updated for buffer layers when the buffer is latched.
-                layer->updateLastLatchTime(latchTime);
-            }
-        }
-    });
-    mForceTransactionDisplayChange = false;
-
-    // The client can continue submitting buffers for offscreen layers, but they will not
-    // be shown on screen. Therefore, we need to latch and release buffers of offscreen
-    // layers to ensure dequeueBuffer doesn't block indefinitely.
-    for (Layer* offscreenLayer : mOffscreenLayers) {
-        offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                         [&](Layer* l) { l->latchAndReleaseBuffer(); });
-    }
-
-    if (!mLayersWithQueuedFrames.empty()) {
-        // mStateLock is needed for latchBuffer as LayerRejecter::reject()
-        // writes to Layer current state. See also b/119481871
-        Mutex::Autolock lock(mStateLock);
-
-        for (const auto& layer : mLayersWithQueuedFrames) {
-            if (layer->willReleaseBufferOnLatch()) {
-                mLayersWithBuffersRemoved.emplace(layer);
-            }
-            if (layer->latchBuffer(visibleRegions, latchTime)) {
-                mLayersPendingRefresh.push_back(layer);
-                newDataLatched = true;
-            }
-            layer->useSurfaceDamage();
-        }
-    }
-
-    mVisibleRegionsDirty |= visibleRegions;
-
-    // If we will need to wake up at some time in the future to deal with a
-    // queued frame that shouldn't be displayed during this vsync period, wake
-    // up during the next vsync period to check again.
-    if (frameQueued && (mLayersWithQueuedFrames.empty() || !newDataLatched)) {
-        scheduleCommit(FrameHint::kNone);
-    }
-
-    // enter boot animation on first buffer latch
-    if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
-        ALOGI("Enter boot animation");
-        mBootStage = BootStage::BOOTANIMATION;
-    }
-
-    if (mLayerMirrorRoots.size() > 0) {
-        mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); });
-    }
-
-    // Only continue with the refresh if there is actually new work to do
-    return !mLayersWithQueuedFrames.empty() && newDataLatched;
-}
-
 status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
                                         const sp<Layer>& layer, const wp<Layer>& parent,
                                         uint32_t* outTransformHint) {
-    if (mNumLayers >= MAX_LAYERS) {
-        ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
-              MAX_LAYERS);
-        static_cast<void>(mScheduler->schedule([=, this] {
-            ALOGE("Dumping layer keeping > 20 children alive:");
-            bool leakingParentLayerFound = false;
-            mDrawingState.traverse([&](Layer* layer) {
-                if (leakingParentLayerFound) {
-                    return;
-                }
-                if (layer->getChildrenCount() > 20) {
-                    leakingParentLayerFound = true;
-                    sp<Layer> parent = sp<Layer>::fromExisting(layer);
-                    while (parent) {
-                        ALOGE("Parent Layer: %s%s", parent->getName().c_str(),
-                              (parent->isHandleAlive() ? "handleAlive" : ""));
-                        parent = parent->getParent();
-                    }
-                    // Sample up to 100 layers
-                    ALOGE("Dumping random sampling of child layers total(%zu): ",
-                          layer->getChildrenCount());
-                    int sampleSize = (layer->getChildrenCount() / 100) + 1;
-                    layer->traverseChildren([&](Layer* layer) {
-                        if (rand() % sampleSize == 0) {
-                            ALOGE("Child Layer: %s%s", layer->getName().c_str(),
-                                  (layer->isHandleAlive() ? "handleAlive" : ""));
-                        }
-                    });
-                }
-            });
-
-            int numLayers = 0;
-            mDrawingState.traverse([&](Layer* layer) { numLayers++; });
-
-            ALOGE("Dumping random sampling of on-screen layers total(%u):", numLayers);
-            mDrawingState.traverse([&](Layer* layer) {
-                // Aim to dump about 200 layers to avoid totally trashing
-                // logcat. On the other hand, if there really are 4096 layers
-                // something has gone totally wrong its probably the most
-                // useful information in logcat.
-                if (rand() % 20 == 13) {
-                    ALOGE("Layer: %s%s", layer->getName().c_str(),
-                          (layer->isHandleAlive() ? "handleAlive" : ""));
-                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
-                }
-            });
-            ALOGE("Dumping random sampling of off-screen layers total(%zu): ",
-                  mOffscreenLayers.size());
-            for (Layer* offscreenLayer : mOffscreenLayers) {
-                if (rand() % 20 == 13) {
-                    ALOGE("Offscreen-layer: %s%s", offscreenLayer->getName().c_str(),
-                          (offscreenLayer->isHandleAlive() ? "handleAlive" : ""));
-                    std::this_thread::sleep_for(std::chrono::milliseconds(5));
-                }
-            }
-        }));
-        return NO_MEMORY;
-    }
-
-    layer->updateTransformHint(mActiveDisplayTransformHint);
     if (outTransformHint) {
         *outTransformHint = mActiveDisplayTransformHint;
     }
@@ -4667,7 +4361,7 @@
     args.layerIdToMirror = LayerHandle::getLayerId(args.mirrorLayerHandle.promote());
     {
         std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
+        mCreatedLayers.emplace_back(layer);
         mNewLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
         args.mirrorLayerHandle.clear();
         args.parentHandle.clear();
@@ -4684,7 +4378,7 @@
 
 uint32_t SurfaceFlinger::clearTransactionFlags(uint32_t mask) {
     uint32_t transactionFlags = mTransactionFlags.fetch_and(~mask);
-    ATRACE_INT("mTransactionFlags", transactionFlags);
+    SFTRACE_INT("mTransactionFlags", transactionFlags);
     return transactionFlags & mask;
 }
 
@@ -4692,7 +4386,7 @@
                                          const sp<IBinder>& applyToken, FrameHint frameHint) {
     mScheduler->modulateVsync({}, &VsyncModulator::setTransactionSchedule, schedule, applyToken);
     uint32_t transactionFlags = mTransactionFlags.fetch_or(mask);
-    ATRACE_INT("mTransactionFlags", transactionFlags);
+    SFTRACE_INT("mTransactionFlags", transactionFlags);
 
     if (const bool scheduled = transactionFlags & mask; !scheduled) {
         scheduleCommit(frameHint);
@@ -4717,8 +4411,8 @@
     // for stability reasons.
     if (!transaction.isAutoTimestamp && desiredPresentTime >= expectedPresentTime &&
         desiredPresentTime < expectedPresentTime + 1s) {
-        ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
-                      desiredPresentTime, expectedPresentTime);
+        SFTRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
+                       desiredPresentTime, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
@@ -4730,242 +4424,150 @@
     // incorrectly as the frame rate of SF changed before it drained the older transactions.
     if (ftl::to_underlying(vsyncId) == FrameTimelineInfo::INVALID_VSYNC_ID &&
         !mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
-        ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
-                      transaction.originUid);
+        SFTRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d",
+                       expectedPresentTime, transaction.originUid);
         return TransactionReadiness::NotReady;
     }
 
     // If the client didn't specify desiredPresentTime, use the vsyncId to determine the
     // expected present time of this transaction.
     if (transaction.isAutoTimestamp && frameIsEarly(expectedPresentTime, vsyncId)) {
-        ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
-                      transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
+        SFTRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
+                       transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
         return TransactionReadiness::NotReady;
     }
 
     return TransactionReadiness::Ready;
 }
 
-TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheckLegacy(
-        const TransactionHandler::TransactionFlushState& flushState) {
-    using TransactionReadiness = TransactionHandler::TransactionReadiness;
-    auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
-                                                                           resolvedState) -> bool {
-        sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
-
-        const auto& transaction = *flushState.transaction;
-        const auto& s = resolvedState.state;
-        // check for barrier frames
-        if (s.bufferData->hasBarrier) {
-            // 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().barrierProducerId > s.bufferData->producerId) {
-                layer->callReleaseBufferCallback(s.bufferData->releaseBufferListener,
-                                                 resolvedState.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_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
-                              layer->getDebugName(), layer->getDrawingState().barrierProducerId,
-                              s.bufferData->producerId);
-                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
-            }
-
-            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_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
-                                  layer->getDebugName(),
-                                  layer->getDrawingState().barrierFrameNumber,
-                                  s.bufferData->barrierFrameNumber);
-                    ready = TransactionReadiness::NotReadyBarrier;
-                    return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-                }
-            }
-        }
-
-        // If backpressure is enabled and we already have a buffer to commit, keep
-        // the transaction in the queue.
-        const bool hasPendingBuffer =
-                flushState.bufferLayersReadyToPresent.contains(s.surface.get());
-        if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_FORMAT("hasPendingBuffer %s", layer->getDebugName());
-            ready = TransactionReadiness::NotReady;
-            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-        }
-
-        const bool acquireFenceAvailable = s.bufferData &&
-                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
-                s.bufferData->acquireFence;
-        const bool fenceSignaled = !acquireFenceAvailable ||
-                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
-        if (!fenceSignaled) {
-            // check fence status
-            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
-                                                                    flushState.firstTransaction) &&
-                    layer->isSimpleBufferUpdate(s);
-
-            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 &&
-                    (flushState.queueProcessTime - transaction.postTime) >
-                            std::chrono::nanoseconds(4s).count()) {
-                    mTransactionHandler
-                            .onTransactionQueueStalled(transaction.id,
-                                                       {.pid = layer->getOwnerPid(),
-                                                        .layerId = static_cast<uint32_t>(
-                                                                layer->getSequence()),
-                                                        .layerName = layer->getDebugName(),
-                                                        .bufferId = s.bufferData->getId(),
-                                                        .frameNumber = s.bufferData->frameNumber});
-                }
-                ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName());
-                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-            }
-        }
-        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
-    });
-    return ready;
-}
-
 TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck(
         const TransactionHandler::TransactionFlushState& flushState) {
     using TransactionReadiness = TransactionHandler::TransactionReadiness;
     auto ready = TransactionReadiness::Ready;
-    flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
-                                                                           resolvedState) -> bool {
-        const frontend::RequestedLayerState* layer =
-                mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
-        const auto& transaction = *flushState.transaction;
-        const auto& s = resolvedState.state;
-        // check for barrier frames
-        if (s.bufferData->hasBarrier) {
-            // 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->barrierProducerId > s.bufferData->producerId) {
-                if (s.bufferData->releaseBufferListener) {
-                    uint32_t currentMaxAcquiredBufferCount =
-                            getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val());
-                    ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
-                                          layer->name.c_str(), s.bufferData->frameNumber);
-                    s.bufferData->releaseBufferListener
-                            ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(),
-                                               s.bufferData->frameNumber},
-                                              s.bufferData->acquireFence
-                                                      ? s.bufferData->acquireFence
-                                                      : Fence::NO_FENCE,
-                                              currentMaxAcquiredBufferCount);
+    flushState.transaction->traverseStatesWithBuffersWhileTrue(
+            [&](const ResolvedComposerState& resolvedState) FTL_FAKE_GUARD(
+                    kMainThreadContext) -> bool {
+                const frontend::RequestedLayerState* layer =
+                        mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
+                const auto& transaction = *flushState.transaction;
+                const auto& s = resolvedState.state;
+                // check for barrier frames
+                if (s.bufferData->hasBarrier) {
+                    // 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->barrierProducerId > s.bufferData->producerId) {
+                        if (s.bufferData->releaseBufferListener) {
+                            uint32_t currentMaxAcquiredBufferCount =
+                                    getMaxAcquiredBufferCountForCurrentRefreshRate(
+                                            layer->ownerUid.val());
+                            SFTRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+                                                   layer->name.c_str(), s.bufferData->frameNumber);
+                            s.bufferData->releaseBufferListener
+                                    ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()
+                                                               ->getId(),
+                                                       s.bufferData->frameNumber},
+                                                      s.bufferData->acquireFence
+                                                              ? s.bufferData->acquireFence
+                                                              : Fence::NO_FENCE,
+                                                      currentMaxAcquiredBufferCount);
+                        }
+
+                        // 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.
+                        SFTRACE_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d",
+                                       layer->name.c_str(), layer->barrierProducerId,
+                                       s.bufferData->producerId);
+                        return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+                    }
+
+                    if (layer->barrierFrameNumber < s.bufferData->barrierFrameNumber) {
+                        const bool willApplyBarrierFrame =
+                                flushState.bufferLayersReadyToPresent.contains(s.surface.get()) &&
+                                ((flushState.bufferLayersReadyToPresent.get(s.surface.get()) >=
+                                  s.bufferData->barrierFrameNumber));
+                        if (!willApplyBarrierFrame) {
+                            SFTRACE_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64
+                                           " > %" PRId64,
+                                           layer->name.c_str(), layer->barrierFrameNumber,
+                                           s.bufferData->barrierFrameNumber);
+                            ready = TransactionReadiness::NotReadyBarrier;
+                            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                        }
+                    }
                 }
 
-                // 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_FORMAT("DeleteStaleBuffer %s barrierProducerId:%d > %d", layer->name.c_str(),
-                              layer->barrierProducerId, s.bufferData->producerId);
-                return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
-            }
-
-            if (layer->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_FORMAT("NotReadyBarrier %s barrierFrameNumber:%" PRId64 " > %" PRId64,
-                                  layer->name.c_str(), layer->barrierFrameNumber,
-                                  s.bufferData->barrierFrameNumber);
-                    ready = TransactionReadiness::NotReadyBarrier;
+                // If backpressure is enabled and we already have a buffer to commit, keep
+                // the transaction in the queue.
+                const bool hasPendingBuffer =
+                        flushState.bufferLayersReadyToPresent.contains(s.surface.get());
+                if (layer->backpressureEnabled() && hasPendingBuffer &&
+                    transaction.isAutoTimestamp) {
+                    SFTRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
+                    ready = TransactionReadiness::NotReady;
                     return TraverseBuffersReturnValues::STOP_TRAVERSAL;
                 }
-            }
-        }
 
-        // If backpressure is enabled and we already have a buffer to commit, keep
-        // the transaction in the queue.
-        const bool hasPendingBuffer =
-                flushState.bufferLayersReadyToPresent.contains(s.surface.get());
-        if (layer->backpressureEnabled() && hasPendingBuffer && transaction.isAutoTimestamp) {
-            ATRACE_FORMAT("hasPendingBuffer %s", layer->name.c_str());
-            ready = TransactionReadiness::NotReady;
-            return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-        }
-
-        const bool acquireFenceAvailable = s.bufferData &&
-                s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
-                s.bufferData->acquireFence;
-        const bool fenceSignaled = !acquireFenceAvailable ||
-                s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
-        if (!fenceSignaled) {
-            // check fence status
-            const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
-                                                                    flushState.firstTransaction) &&
-                    layer->isSimpleBufferUpdate(s);
-            if (allowLatchUnsignaled) {
-                ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str());
-                ready = TransactionReadiness::NotReadyUnsignaled;
-            } else {
-                ready = TransactionReadiness::NotReady;
-                auto& listener = s.bufferData->releaseBufferListener;
-                if (listener &&
-                    (flushState.queueProcessTime - transaction.postTime) >
-                            std::chrono::nanoseconds(4s).count()) {
-                    mTransactionHandler
-                            .onTransactionQueueStalled(transaction.id,
-                                                       {.pid = layer->ownerPid.val(),
-                                                        .layerId = layer->id,
-                                                        .layerName = layer->name,
-                                                        .bufferId = s.bufferData->getId(),
-                                                        .frameNumber = s.bufferData->frameNumber});
+                const bool acquireFenceAvailable = s.bufferData &&
+                        s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+                        s.bufferData->acquireFence;
+                const bool fenceSignaled = !acquireFenceAvailable ||
+                        s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+                if (!fenceSignaled) {
+                    // check fence status
+                    const bool allowLatchUnsignaled =
+                            shouldLatchUnsignaled(s, transaction.states.size(),
+                                                  flushState.firstTransaction) &&
+                            layer->isSimpleBufferUpdate(s);
+                    if (allowLatchUnsignaled) {
+                        SFTRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s",
+                                       layer->name.c_str());
+                        ready = TransactionReadiness::NotReadyUnsignaled;
+                    } else {
+                        ready = TransactionReadiness::NotReady;
+                        auto& listener = s.bufferData->releaseBufferListener;
+                        if (listener &&
+                            (flushState.queueProcessTime - transaction.postTime) >
+                                    std::chrono::nanoseconds(4s).count()) {
+                            mTransactionHandler
+                                    .onTransactionQueueStalled(transaction.id,
+                                                               {.pid = layer->ownerPid.val(),
+                                                                .layerId = layer->id,
+                                                                .layerName = layer->name,
+                                                                .bufferId = s.bufferData->getId(),
+                                                                .frameNumber =
+                                                                        s.bufferData->frameNumber});
+                        }
+                        SFTRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+                        return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+                    }
                 }
-                ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
-                return TraverseBuffersReturnValues::STOP_TRAVERSAL;
-            }
-        }
-        return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
-    });
+                return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+            });
     return ready;
 }
 
 void SurfaceFlinger::addTransactionReadyFilters() {
     mTransactionHandler.addTransactionReadyFilter(
             std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1));
-    if (mLayerLifecycleManagerEnabled) {
-        mTransactionHandler.addTransactionReadyFilter(
-                std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this,
-                          std::placeholders::_1));
-    } else {
-        mTransactionHandler.addTransactionReadyFilter(
-                std::bind(&SurfaceFlinger::transactionReadyBufferCheckLegacy, this,
-                          std::placeholders::_1));
-    }
+    mTransactionHandler.addTransactionReadyFilter(
+            std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this, std::placeholders::_1));
 }
 
 // For tests only
-bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) {
+bool SurfaceFlinger::flushTransactionQueues() {
     mTransactionHandler.collectTransactions();
     std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
-    return applyTransactions(transactions, vsyncId);
+    return applyTransactions(transactions);
 }
 
-bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
-                                       VsyncId vsyncId) {
+bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions) {
     Mutex::Autolock lock(mStateLock);
-    return applyTransactionsLocked(transactions, vsyncId);
+    return applyTransactionsLocked(transactions);
 }
 
-bool SurfaceFlinger::applyTransactionsLocked(std::vector<TransactionState>& transactions,
-                                             VsyncId vsyncId) {
+bool SurfaceFlinger::applyTransactionsLocked(std::vector<TransactionState>& transactions) {
     bool needsTraversal = false;
     // Now apply all transactions.
     for (auto& transaction : transactions) {
@@ -5009,22 +4611,22 @@
 bool SurfaceFlinger::shouldLatchUnsignaled(const layer_state_t& state, size_t numStates,
                                            bool firstTransaction) const {
     if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
-        ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
+        SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
         return false;
     }
 
     // We only want to latch unsignaled when a single layer is updated in this
     // transaction (i.e. not a blast sync transaction).
     if (numStates != 1) {
-        ATRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
+        SFTRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
         return false;
     }
 
     if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
         if (!firstTransaction) {
-            ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first "
-                                  "transaction)",
-                                  __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; not first "
+                                   "transaction)",
+                                   __func__);
             return false;
         }
 
@@ -5032,9 +4634,9 @@
         // as it leads to jank due to RenderEngine waiting for unsignaled buffer
         // or window animations being slow.
         if (mScheduler->vsyncModulator().isVsyncConfigEarly()) {
-            ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
-                                  "isVsyncConfigEarly)",
-                                  __func__);
+            SFTRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
+                                   "isVsyncConfigEarly)",
+                                   __func__);
             return false;
         }
     }
@@ -5044,22 +4646,22 @@
 
 status_t SurfaceFlinger::setTransactionState(
         const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-        const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+        Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
         InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
         const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
         const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
         const std::vector<uint64_t>& mergedTransactionIds) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     IPCThreadState* ipc = IPCThreadState::self();
     const int originPid = ipc->getCallingPid();
     const int originUid = ipc->getCallingUid();
     uint32_t permissions = LayerStatePermissions::getTransactionPermissions(originPid, originUid);
-    for (auto composerState : states) {
+    for (auto& composerState : states) {
         composerState.state.sanitize(permissions);
     }
 
-    for (DisplayState display : displays) {
+    for (DisplayState& display : displays) {
         display.sanitize(permissions);
     }
 
@@ -5154,7 +4756,13 @@
     }(state.flags);
 
     const auto frameHint = state.isFrameActive() ? FrameHint::kActive : FrameHint::kNone;
-    mTransactionHandler.queueTransaction(std::move(state));
+    {
+        // Transactions are added via a lockless queue and does not need to be added from the main
+        // thread.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.queueTransaction(std::move(state));
+    }
+
     for (const auto& [displayId, data] : mNotifyExpectedPresentMap) {
         if (data.hintStatus.load() == NotifyExpectedPresentHintStatus::ScheduleOnTx) {
             scheduleNotifyExpectedPresentHint(displayId, VsyncId{frameTimelineInfo.vsyncId});
@@ -5174,11 +4782,6 @@
                                            const std::vector<ListenerCallbacks>& listenerCallbacks,
                                            int originPid, int originUid, uint64_t transactionId) {
     uint32_t transactionFlags = 0;
-    if (!mLayerLifecycleManagerEnabled) {
-        for (DisplayState& display : displays) {
-            transactionFlags |= setDisplayStateLocked(display);
-        }
-    }
 
     // start and end registration for listeners w/ no surface so they can get their callback.  Note
     // that listeners with SurfaceControls will start registration during setClientStateLocked
@@ -5186,32 +4789,11 @@
     for (const auto& listener : listenerCallbacks) {
         mTransactionCallbackInvoker.addEmptyTransaction(listener);
     }
-    nsecs_t now = systemTime();
     uint32_t clientStateFlags = 0;
     for (auto& resolvedState : states) {
-        if (mLegacyFrontEndEnabled) {
-            clientStateFlags |=
-                    setClientStateLocked(frameTimelineInfo, resolvedState, desiredPresentTime,
-                                         isAutoTimestamp, postTime, transactionId);
-
-        } else /*mLayerLifecycleManagerEnabled*/ {
-            clientStateFlags |= updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState,
-                                                             desiredPresentTime, isAutoTimestamp,
-                                                             postTime, transactionId);
-        }
-        if ((flags & eAnimation) && resolvedState.state.surface) {
-            if (const auto layer = LayerHandle::getLayer(resolvedState.state.surface)) {
-                const auto layerProps = scheduler::LayerProps{
-                        .visible = layer->isVisible(),
-                        .bounds = layer->getBounds(),
-                        .transform = layer->getTransform(),
-                        .setFrameRateVote = layer->getFrameRateForLayerTree(),
-                        .frameRateSelectionPriority = layer->getFrameRateSelectionPriority(),
-                        .isFrontBuffered = layer->isFrontBuffered(),
-                };
-                layer->recordLayerHistoryAnimationTx(layerProps, now);
-            }
-        }
+        clientStateFlags |=
+                updateLayerCallbacksAndStats(frameTimelineInfo, resolvedState, desiredPresentTime,
+                                             isAutoTimestamp, postTime, transactionId);
     }
 
     transactionFlags |= clientStateFlags;
@@ -5268,7 +4850,7 @@
     }
 
     mFrontEndDisplayInfosChanged = mTransactionFlags & eDisplayTransactionNeeded;
-    if (mFrontEndDisplayInfosChanged && !mLegacyFrontEndEnabled) {
+    if (mFrontEndDisplayInfosChanged) {
         processDisplayChangesLocked();
         mFrontEndDisplayInfos.clear();
         for (const auto& [_, display] : mDisplays) {
@@ -5346,375 +4928,6 @@
     return true;
 }
 
-uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTimelineInfo,
-                                              ResolvedComposerState& composerState,
-                                              int64_t desiredPresentTime, bool isAutoTimestamp,
-                                              int64_t postTime, uint64_t transactionId) {
-    layer_state_t& s = composerState.state;
-
-    std::vector<ListenerCallbacks> filteredListeners;
-    for (auto& listener : s.listeners) {
-        // Starts a registration but separates the callback ids according to callback type. This
-        // allows the callback invoker to send on latch callbacks earlier.
-        // note that startRegistration will not re-register if the listener has
-        // already be registered for a prior surface control
-
-        ListenerCallbacks onCommitCallbacks = listener.filter(CallbackId::Type::ON_COMMIT);
-        if (!onCommitCallbacks.callbackIds.empty()) {
-            filteredListeners.push_back(onCommitCallbacks);
-        }
-
-        ListenerCallbacks onCompleteCallbacks = listener.filter(CallbackId::Type::ON_COMPLETE);
-        if (!onCompleteCallbacks.callbackIds.empty()) {
-            filteredListeners.push_back(onCompleteCallbacks);
-        }
-    }
-
-    const uint64_t what = s.what;
-    uint32_t flags = 0;
-    sp<Layer> layer = nullptr;
-    if (s.surface) {
-        layer = LayerHandle::getLayer(s.surface);
-    } else {
-        // The client may provide us a null handle. Treat it as if the layer was removed.
-        ALOGW("Attempt to set client state with a null layer handle");
-    }
-    if (layer == nullptr) {
-        for (auto& [listener, callbackIds] : s.listeners) {
-            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
-                                                                                   callbackIds,
-                                                                                   s.surface),
-                                                          std::vector<JankData>());
-        }
-        return 0;
-    }
-    MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock);
-
-    ui::LayerStack oldLayerStack = layer->getLayerStack(LayerVector::StateSet::Current);
-
-    // Only set by BLAST adapter layers
-    if (what & layer_state_t::eProducerDisconnect) {
-        layer->onDisconnect();
-    }
-
-    if (what & layer_state_t::ePositionChanged) {
-        if (layer->setPosition(s.x, s.y)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eLayerChanged) {
-        // NOTE: index needs to be calculated before we update the state
-        const auto& p = layer->getParent();
-        if (p == nullptr) {
-            ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
-            if (layer->setLayer(s.z) && idx >= 0) {
-                mCurrentState.layersSortedByZ.removeAt(idx);
-                mCurrentState.layersSortedByZ.add(layer);
-                // we need traversal (state changed)
-                // AND transaction (list changed)
-                flags |= eTransactionNeeded|eTraversalNeeded;
-            }
-        } else {
-            if (p->setChildLayer(layer, s.z)) {
-                flags |= eTransactionNeeded|eTraversalNeeded;
-            }
-        }
-    }
-    if (what & layer_state_t::eRelativeLayerChanged) {
-        // NOTE: index needs to be calculated before we update the state
-        const auto& p = layer->getParent();
-        const auto& relativeHandle = s.relativeLayerSurfaceControl ?
-                s.relativeLayerSurfaceControl->getHandle() : nullptr;
-        if (p == nullptr) {
-            ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
-            if (layer->setRelativeLayer(relativeHandle, s.z) &&
-                idx >= 0) {
-                mCurrentState.layersSortedByZ.removeAt(idx);
-                mCurrentState.layersSortedByZ.add(layer);
-                // we need traversal (state changed)
-                // AND transaction (list changed)
-                flags |= eTransactionNeeded|eTraversalNeeded;
-            }
-        } else {
-            if (p->setChildRelativeLayer(layer, relativeHandle, s.z)) {
-                flags |= eTransactionNeeded|eTraversalNeeded;
-            }
-        }
-    }
-    if (what & layer_state_t::eAlphaChanged) {
-        if (layer->setAlpha(s.color.a)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eColorChanged) {
-        if (layer->setColor(s.color.rgb)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eColorTransformChanged) {
-        if (layer->setColorTransform(s.colorTransform)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eBackgroundColorChanged) {
-        if (layer->setBackgroundColor(s.bgColor.rgb, s.bgColor.a, s.bgColorDataspace)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eMatrixChanged) {
-        if (layer->setMatrix(s.matrix)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eTransparentRegionChanged) {
-        if (layer->setTransparentRegionHint(s.transparentRegion))
-            flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eFlagsChanged) {
-        if (layer->setFlags(s.flags, s.mask)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eCornerRadiusChanged) {
-        if (layer->setCornerRadius(s.cornerRadius))
-            flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eBackgroundBlurRadiusChanged && mSupportsBlur) {
-        if (layer->setBackgroundBlurRadius(s.backgroundBlurRadius)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eBlurRegionsChanged) {
-        if (layer->setBlurRegions(s.blurRegions)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eRenderBorderChanged) {
-        if (layer->enableBorder(s.borderEnabled, s.borderWidth, s.borderColor)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eLayerStackChanged) {
-        ssize_t idx = mCurrentState.layersSortedByZ.indexOf(layer);
-        // We only allow setting layer stacks for top level layers,
-        // everything else inherits layer stack from its parent.
-        if (layer->hasParent()) {
-            ALOGE("Attempt to set layer stack on layer with parent (%s) is invalid",
-                  layer->getDebugName());
-        } else if (idx < 0) {
-            ALOGE("Attempt to set layer stack on layer without parent (%s) that "
-                  "that also does not appear in the top level layer list. Something"
-                  " has gone wrong.",
-                  layer->getDebugName());
-        } else if (layer->setLayerStack(s.layerStack)) {
-            mCurrentState.layersSortedByZ.removeAt(idx);
-            mCurrentState.layersSortedByZ.add(layer);
-            // we need traversal (state changed)
-            // AND transaction (list changed)
-            flags |= eTransactionNeeded | eTraversalNeeded | eTransformHintUpdateNeeded;
-        }
-    }
-    if (what & layer_state_t::eBufferTransformChanged) {
-        if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eTransformToDisplayInverseChanged) {
-        if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse))
-            flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eCropChanged) {
-        if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eDataspaceChanged) {
-        if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eSurfaceDamageRegionChanged) {
-        if (layer->setSurfaceDamageRegion(s.surfaceDamageRegion)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eApiChanged) {
-        if (layer->setApi(s.api)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
-            flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eInputInfoChanged) {
-        layer->setInputInfo(*s.windowInfoHandle->getInfo());
-        flags |= eTraversalNeeded;
-    }
-    std::optional<nsecs_t> dequeueBufferTimestamp;
-    if (what & layer_state_t::eMetadataChanged) {
-        dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
-
-        if (const int32_t gameMode = s.metadata.getInt32(gui::METADATA_GAME_MODE, -1);
-            gameMode != -1) {
-            // The transaction will be received on the Task layer and needs to be applied to all
-            // child layers. Child layers that are added at a later point will obtain the game mode
-            // info through addChild().
-            layer->setGameModeForTree(static_cast<GameMode>(gameMode));
-        }
-
-        if (layer->setMetadata(s.metadata)) {
-            flags |= eTraversalNeeded;
-            mLayerMetadataSnapshotNeeded = true;
-        }
-    }
-    if (what & layer_state_t::eColorSpaceAgnosticChanged) {
-        if (layer->setColorSpaceAgnostic(s.colorSpaceAgnostic)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eShadowRadiusChanged) {
-        if (layer->setShadowRadius(s.shadowRadius)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
-        const auto compatibility =
-                Layer::FrameRate::convertCompatibility(s.defaultFrameRateCompatibility);
-
-        if (layer->setDefaultFrameRateCompatibility(compatibility)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eFrameRateSelectionPriority) {
-        if (layer->setFrameRateSelectionPriority(s.frameRateSelectionPriority)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eFrameRateChanged) {
-        const auto compatibility =
-            Layer::FrameRate::convertCompatibility(s.frameRateCompatibility);
-        const auto strategy =
-            Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
-
-        if (layer->setFrameRate(Layer::FrameRate::FrameRateVote(Fps::fromValue(s.frameRate),
-                                                                compatibility, strategy))) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eFrameRateCategoryChanged) {
-        const FrameRateCategory category = Layer::FrameRate::convertCategory(s.frameRateCategory);
-        if (layer->setFrameRateCategory(category, s.frameRateCategorySmoothSwitchOnly)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
-        const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
-                scheduler::LayerInfo::convertFrameRateSelectionStrategy(
-                        s.frameRateSelectionStrategy);
-        if (layer->setFrameRateSelectionStrategy(strategy)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eFixedTransformHintChanged) {
-        if (layer->setFixedTransformHint(s.fixedTransformHint)) {
-            flags |= eTraversalNeeded | eTransformHintUpdateNeeded;
-        }
-    }
-    if (what & layer_state_t::eAutoRefreshChanged) {
-        layer->setAutoRefresh(s.autoRefresh);
-    }
-    if (what & layer_state_t::eDimmingEnabledChanged) {
-        if (layer->setDimmingEnabled(s.dimmingEnabled)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
-        if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eDesiredHdrHeadroomChanged) {
-        if (layer->setDesiredHdrHeadroom(s.desiredHdrSdrRatio)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eCachingHintChanged) {
-        if (layer->setCachingHint(s.cachingHint)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eHdrMetadataChanged) {
-        if (layer->setHdrMetadata(s.hdrMetadata)) flags |= eTraversalNeeded;
-    }
-    if (what & layer_state_t::eTrustedOverlayChanged) {
-        if (layer->setTrustedOverlay(s.isTrustedOverlay)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eStretchChanged) {
-        if (layer->setStretchEffect(s.stretchEffect)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eBufferCropChanged) {
-        if (layer->setBufferCrop(s.bufferCrop)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eDestinationFrameChanged) {
-        if (layer->setDestinationFrame(s.destinationFrame)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-    if (what & layer_state_t::eDropInputModeChanged) {
-        if (layer->setDropInputMode(s.dropInputMode)) {
-            flags |= eTraversalNeeded;
-            mUpdateInputInfo = true;
-        }
-    }
-    // This has to happen after we reparent children because when we reparent to null we remove
-    // child layers from current state and remove its relative z. If the children are reparented in
-    // the same transaction, then we have to make sure we reparent the children first so we do not
-    // lose its relative z order.
-    if (what & layer_state_t::eReparent) {
-        bool hadParent = layer->hasParent();
-        auto parentHandle = (s.parentSurfaceControlForChild)
-                ? s.parentSurfaceControlForChild->getHandle()
-                : nullptr;
-        if (layer->reparent(parentHandle)) {
-            if (!hadParent) {
-                layer->setIsAtRoot(false);
-                mCurrentState.layersSortedByZ.remove(layer);
-            }
-            flags |= eTransactionNeeded | eTraversalNeeded;
-        }
-    }
-    std::vector<sp<CallbackHandle>> callbackHandles;
-    if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
-        for (auto& [listener, callbackIds] : filteredListeners) {
-            callbackHandles.emplace_back(
-                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
-        }
-    }
-
-    if (what & layer_state_t::eBufferChanged) {
-        if (layer->setBuffer(composerState.externalTexture, *s.bufferData, postTime,
-                             desiredPresentTime, isAutoTimestamp, dequeueBufferTimestamp,
-                             frameTimelineInfo)) {
-            flags |= eTraversalNeeded;
-        }
-    } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
-        layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
-    }
-
-    if ((what & layer_state_t::eBufferChanged) == 0) {
-        layer->setDesiredPresentTime(desiredPresentTime, isAutoTimestamp);
-    }
-
-    if (what & layer_state_t::eTrustedPresentationInfoChanged) {
-        if (layer->setTrustedPresentationInfo(s.trustedPresentationThresholds,
-                                              s.trustedPresentationListener)) {
-            flags |= eTraversalNeeded;
-        }
-    }
-
-    if (what & layer_state_t::eFlushJankData) {
-        // Do nothing. Processing the transaction completed listeners currently cause the flush.
-    }
-
-    if (layer->setTransactionCompletedListeners(callbackHandles,
-                                                layer->willPresentCurrentTransaction() ||
-                                                        layer->willReleaseBufferOnLatch())) {
-        flags |= eTraversalNeeded;
-    }
-
-    // Do not put anything that updates layer state or modifies flags after
-    // setTransactionCompletedListener
-
-    // if the layer has been parented on to a new display, update its transform hint.
-    if (((flags & eTransformHintUpdateNeeded) == 0) &&
-        oldLayerStack != layer->getLayerStack(LayerVector::StateSet::Current)) {
-        flags |= eTransformHintUpdateNeeded;
-    }
-
-    return flags;
-}
-
 uint32_t SurfaceFlinger::updateLayerCallbacksAndStats(const FrameTimelineInfo& frameTimelineInfo,
                                                       ResolvedComposerState& composerState,
                                                       int64_t desiredPresentTime,
@@ -5751,20 +4964,14 @@
     }
     if (layer == nullptr) {
         for (auto& [listener, callbackIds] : s.listeners) {
-            mTransactionCallbackInvoker.addCallbackHandle(sp<CallbackHandle>::make(listener,
-                                                                                   callbackIds,
-                                                                                   s.surface),
-                                                          std::vector<JankData>());
+            mTransactionCallbackInvoker.addCallbackHandle(
+                    sp<CallbackHandle>::make(listener, callbackIds, s.surface));
         }
         return 0;
     }
     if (what & layer_state_t::eProducerDisconnect) {
         layer->onDisconnect();
     }
-    std::optional<nsecs_t> dequeueBufferTimestamp;
-    if (what & layer_state_t::eMetadataChanged) {
-        dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
-    }
 
     std::vector<sp<CallbackHandle>> callbackHandles;
     if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
@@ -5773,6 +4980,17 @@
                     sp<CallbackHandle>::make(listener, callbackIds, s.surface));
         }
     }
+
+    frontend::LayerSnapshot* snapshot = nullptr;
+    gui::GameMode gameMode = gui::GameMode::Unsupported;
+    if (what & (layer_state_t::eSidebandStreamChanged | layer_state_t::eBufferChanged) ||
+        frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
+        snapshot = mLayerSnapshotBuilder.getSnapshot(layer->sequence);
+        if (snapshot) {
+            gameMode = snapshot->gameMode;
+        }
+    }
+
     // TODO(b/238781169) remove after screenshot refactor, currently screenshots
     // requires to read drawing state from binder thread. So we need to fix that
     // before removing this.
@@ -5787,7 +5005,7 @@
         if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eSidebandStreamChanged) {
-        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+        if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime, gameMode))
             flags |= eTraversalNeeded;
     }
     if (what & layer_state_t::eDataspaceChanged) {
@@ -5805,19 +5023,17 @@
     }
     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)) {
+                             desiredPresentTime, isAutoTimestamp, frameTimelineInfo, gameMode)) {
             flags |= eTraversalNeeded;
         }
-        mLayersWithQueuedFrames.emplace(layer);
+        mLayersWithQueuedFrames.emplace(layer, gameMode);
     } else if (frameTimelineInfo.vsyncId != FrameTimelineInfo::INVALID_VSYNC_ID) {
-        layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(frameTimelineInfo, postTime, gameMode);
     }
 
     if ((what & layer_state_t::eBufferChanged) == 0) {
@@ -5831,6 +5047,10 @@
         }
     }
 
+    if (what & layer_state_t::eBufferReleaseChannelChanged) {
+        layer->setBufferReleaseChannel(s.bufferReleaseChannel);
+    }
+
     const auto& requestedLayerState = mLayerLifecycleManager.getLayerFromId(layer->getSequence());
     bool willPresentCurrentTransaction = requestedLayerState &&
             (requestedLayerState->hasReadyFrame() ||
@@ -5869,8 +5089,6 @@
         if (result != NO_ERROR) {
             return result;
         }
-
-        mirrorLayer->setClonedChild(mirrorFrom->createClone(mirrorLayer->getSequence()));
     }
 
     outResult.layerId = mirrorLayer->sequence;
@@ -5906,21 +5124,15 @@
         mirrorArgs.addToRoot = true;
         mirrorArgs.layerStackToMirror = layerStack;
         result = createEffectLayer(mirrorArgs, &outResult.handle, &rootMirrorLayer);
+        if (result != NO_ERROR) {
+            return result;
+        }
         outResult.layerId = rootMirrorLayer->sequence;
         outResult.layerName = String16(rootMirrorLayer->getDebugName());
-        result |= addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */,
+        addClientLayer(mirrorArgs, outResult.handle, rootMirrorLayer /* layer */,
                                  nullptr /* parent */, nullptr /* outTransformHint */);
     }
 
-    if (result != NO_ERROR) {
-        return result;
-    }
-
-    if (mLegacyFrontEndEnabled) {
-        std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
-        mMirrorDisplays.emplace_back(layerStack, outResult.handle, args.client);
-    }
-
     setTransactionFlags(eTransactionFlushNeeded);
     return NO_ERROR;
 }
@@ -5938,6 +5150,9 @@
             [[fallthrough]];
         case ISurfaceComposerClient::eFXSurfaceEffect: {
             result = createBufferStateLayer(args, &outResult.handle, &layer);
+            if (result != NO_ERROR) {
+                return result;
+            }
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
             if (pendingBufferCounter) {
                 std::string counterName = layer->getPendingBufferCounterName();
@@ -5977,6 +5192,9 @@
 
 status_t SurfaceFlinger::createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* handle,
                                                 sp<Layer>* outLayer) {
+    if (checkLayerLeaks() != NO_ERROR) {
+        return NO_MEMORY;
+    }
     *outLayer = getFactory().createBufferStateLayer(args);
     *handle = (*outLayer)->getHandle();
     return NO_ERROR;
@@ -5984,32 +5202,54 @@
 
 status_t SurfaceFlinger::createEffectLayer(const LayerCreationArgs& args, sp<IBinder>* handle,
                                            sp<Layer>* outLayer) {
+    if (checkLayerLeaks() != NO_ERROR) {
+        return NO_MEMORY;
+    }
     *outLayer = getFactory().createEffectLayer(args);
     *handle = (*outLayer)->getHandle();
     return NO_ERROR;
 }
 
-void SurfaceFlinger::markLayerPendingRemovalLocked(const sp<Layer>& layer) {
-    mLayersPendingRemoval.add(layer);
-    mLayersRemoved = true;
-    setTransactionFlags(eTransactionNeeded);
+status_t SurfaceFlinger::checkLayerLeaks() {
+    if (mNumLayers >= MAX_LAYERS) {
+        static std::atomic<nsecs_t> lasttime{0};
+        nsecs_t now = systemTime();
+        if (lasttime != 0 && ns2s(now - lasttime.load()) < 10) {
+            ALOGE("CreateLayer already dumped 10s before");
+            return NO_MEMORY;
+        } else {
+            lasttime = now;
+        }
+
+        ALOGE("CreateLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
+              MAX_LAYERS);
+        static_cast<void>(mScheduler->schedule([&]() FTL_FAKE_GUARD(kMainThreadContext) {
+            ALOGE("Dumping on-screen layers.");
+            mLayerHierarchyBuilder.dumpLayerSample(mLayerHierarchyBuilder.getHierarchy());
+            ALOGE("Dumping off-screen layers.");
+            mLayerHierarchyBuilder.dumpLayerSample(mLayerHierarchyBuilder.getOffscreenHierarchy());
+        }));
+        return NO_MEMORY;
+    }
+    return NO_ERROR;
 }
 
 void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
     {
-        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
+        // Used to remove stalled transactions which uses an internal lock.
+        ftl::FakeGuard guard(kMainThreadContext);
+        mTransactionHandler.onLayerDestroyed(layerId);
     }
+    JankTracker::flushJankData(layerId);
 
-    mTransactionHandler.onLayerDestroyed(layerId);
+    std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+    mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
 
-    Mutex::Autolock lock(mStateLock);
-    markLayerPendingRemovalLocked(layer);
+    Mutex::Autolock stateLock(mStateLock);
     layer->onHandleDestroyed();
     mBufferCountTracker.remove(handle);
     layer.clear();
-
-    setTransactionFlags(eTransactionFlushNeeded);
+    setTransactionFlags(eTransactionFlushNeeded | eTransactionNeeded);
 }
 
 void SurfaceFlinger::initializeDisplays() {
@@ -6031,9 +5271,7 @@
     std::vector<TransactionState> transactions;
     transactions.emplace_back(state);
 
-    if (mLegacyFrontEndEnabled) {
-        applyTransactions(transactions, VsyncId{0});
-    } else {
+    {
         Mutex::Autolock lock(mStateLock);
         applyAndCommitDisplayTransactionStatesLocked(transactions);
     }
@@ -6049,8 +5287,11 @@
         // Power on all displays. The primary display is first, so becomes the active display. Also,
         // the DisplayCapability set of a display is populated on its first powering on. Do this now
         // before responding to any Binder query from DisplayManager about display capabilities.
-        for (const auto& [id, display] : mPhysicalDisplays) {
-            setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+        // Additionally, do not turn on displays if the boot should be quiescent.
+        if (!mSkipPowerOnForQuiescent) {
+            for (const auto& [id, display] : mPhysicalDisplays) {
+                setPowerModeInternal(getDisplayDeviceLocked(id), hal::PowerMode::ON);
+            }
         }
     }
 }
@@ -6209,6 +5450,7 @@
 void SurfaceFlinger::setPowerMode(const sp<IBinder>& displayToken, int mode) {
     auto future = mScheduler->schedule([=, this]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(
                                                kMainThreadContext) {
+        mSkipPowerOnForQuiescent = false;
         const auto display = getDisplayDeviceLocked(displayToken);
         if (!display) {
             ALOGE("Attempt to set power mode %d for invalid display token %p", mode,
@@ -6261,9 +5503,9 @@
             {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
             {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
             {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
-            {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
-            {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
-            {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+            {"--latency"s, argsMainThreadDumper(&SurfaceFlinger::dumpStats)},
+            {"--latency-clear"s, argsMainThreadDumper(&SurfaceFlinger::clearStats)},
+            {"--list"s, mainThreadDumper(&SurfaceFlinger::listLayers)},
             {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
             {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
             {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
@@ -6278,15 +5520,23 @@
         return NO_ERROR;
     }
 
-    // Traversal of drawing state must happen on the main thread.
-    // Otherwise, SortedVector may have shared ownership during concurrent
-    // traversals, which can result in use-after-frees.
+    // Collect debug data from main thread
     std::string compositionLayers;
     mScheduler
             ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                 dumpVisibleFrontEnd(compositionLayers);
             })
             .get();
+    // get window info listener data without the state lock
+    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+    compositionLayers.append("Window Infos:\n");
+    StringAppendF(&compositionLayers, "  max send vsync id: %" PRId64 "\n",
+                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
+    StringAppendF(&compositionLayers, "  max send delay (ns): %" PRId64 " ns\n",
+                  windowInfosDebug.maxSendDelayDuration);
+    StringAppendF(&compositionLayers, "  unsent messages: %zu\n",
+                  windowInfosDebug.pendingMessageCount);
+    compositionLayers.append("\n");
     dumpAll(args, compositionLayers, result);
     write(fd, result.c_str(), result.size());
     return NO_ERROR;
@@ -6296,28 +5546,29 @@
     return doDump(fd, DumpArgs(), asProto);
 }
 
-void SurfaceFlinger::listLayersLocked(std::string& result) const {
-    mCurrentState.traverseInZOrder(
-            [&](Layer* layer) { StringAppendF(&result, "%s\n", layer->getDebugName()); });
+void SurfaceFlinger::listLayers(std::string& result) const {
+    for (const auto& layer : mLayerLifecycleManager.getLayers()) {
+        StringAppendF(&result, "%s\n", layer->getDebugString().c_str());
+    }
 }
 
-void SurfaceFlinger::dumpStatsLocked(const DumpArgs& args, std::string& result) const {
-    StringAppendF(&result, "%" PRId64 "\n", getVsyncPeriodFromHWC());
+void SurfaceFlinger::dumpStats(const DumpArgs& args, std::string& result) const {
+    StringAppendF(&result, "%" PRId64 "\n", mScheduler->getPacesetterVsyncPeriod().ns());
     if (args.size() < 2) return;
 
     const auto name = String8(args[1]);
-    mCurrentState.traverseInZOrder([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (layer->getName() == name.c_str()) {
             layer->dumpFrameStats(result);
         }
     });
 }
 
-void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) {
+void SurfaceFlinger::clearStats(const DumpArgs& args, std::string&) {
     const bool clearAll = args.size() < 2;
     const auto name = clearAll ? String8() : String8(args[1]);
 
-    mCurrentState.traverse([&](Layer* layer) {
+    traverseLegacyLayers([&](Layer* layer) {
         if (clearAll || layer->getName() == name.c_str()) {
             layer->clearFrameStats();
         }
@@ -6337,8 +5588,8 @@
     if (now - sTimestamp < 30min) return;
     sTimestamp = now;
 
-    ATRACE_CALL();
-    mDrawingState.traverse([&](Layer* layer) { layer->logFrameStats(); });
+    SFTRACE_CALL();
+    traverseLegacyLayers([&](Layer* layer) { layer->logFrameStats(); });
 }
 
 void SurfaceFlinger::appendSfConfigString(std::string& result) const {
@@ -6362,11 +5613,6 @@
     // TODO(b/241285876): Move to DisplayModeController.
     dumper.dump("debugDisplayModeSetByBackdoor"sv, mDebugDisplayModeSetByBackdoor);
     dumper.eol();
-
-    StringAppendF(&result,
-                  "         present offset: %9" PRId64 " ns\t        VSYNC period: %9" PRId64
-                  " ns\n\n",
-                  dispSyncPresentTimeOffset, getVsyncPeriodFromHWC());
 }
 
 void SurfaceFlinger::dumpEvents(std::string& result) const {
@@ -6474,18 +5720,19 @@
     StringAppendF(&result, "DisplayColorSetting: %s\n",
                   decodeDisplayColorSetting(mDisplayColorSetting).c_str());
 
-    // TODO: print out if wide-color mode is active or not
+    // TODO: print out if wide-color mode is active or not.
 
     for (const auto& [id, display] : mPhysicalDisplays) {
         StringAppendF(&result, "Display %s color modes:\n", to_string(id).c_str());
         for (const auto mode : display.snapshot().colorModes()) {
-            StringAppendF(&result, "    %s (%d)\n", decodeColorMode(mode).c_str(), mode);
+            StringAppendF(&result, "    %s (%d)\n", decodeColorMode(mode).c_str(),
+                          fmt::underlying(mode));
         }
 
         if (const auto display = getDisplayDeviceLocked(id)) {
             ui::ColorMode currentMode = display->getCompositionDisplay()->getState().colorMode;
             StringAppendF(&result, "    Current color mode: %s (%d)\n",
-                          decodeColorMode(currentMode).c_str(), currentMode);
+                          decodeColorMode(currentMode).c_str(), fmt::underlying(currentMode));
         }
     }
     result.append("\n");
@@ -6528,53 +5775,35 @@
 }
 
 void SurfaceFlinger::dumpVisibleFrontEnd(std::string& result) {
-    if (!mLayerLifecycleManagerEnabled) {
-        StringAppendF(&result, "Composition layers\n");
-        mDrawingState.traverseInZOrder([&](Layer* layer) {
-            auto* compositionState = layer->getCompositionState();
-            if (!compositionState || !compositionState->isVisible) return;
-            android::base::StringAppendF(&result, "* Layer %p (%s)\n", layer,
-                                         layer->getDebugName() ? layer->getDebugName()
-                                                               : "<unknown>");
-            compositionState->dump(result);
-        });
-
-        StringAppendF(&result, "Offscreen Layers\n");
-        for (Layer* offscreenLayer : mOffscreenLayers) {
-            offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                     [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
-        }
-    } else {
-        std::ostringstream out;
-        out << "\nComposition list\n";
-        ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-        mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
-                    if (snapshot->hasSomethingToDraw()) {
-                        if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
-                            lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
-                            out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
-                        }
-                        out << "  " << *snapshot << "\n";
+    std::ostringstream out;
+    out << "\nComposition list\n";
+    ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+    mLayerSnapshotBuilder.forEachVisibleSnapshot(
+            [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                if (snapshot->hasSomethingToDraw()) {
+                    if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+                        lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+                        out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
                     }
-                });
+                    out << "  " << *snapshot << "\n";
+                }
+            });
 
-        out << "\nInput list\n";
-        lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
-        mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
-                lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
-                out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
-            }
-            out << "  " << snapshot << "\n";
-        });
+    out << "\nInput list\n";
+    lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+    mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+        if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+            lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+            out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+        }
+        out << "  " << snapshot << "\n";
+    });
 
-        out << "\nLayer Hierarchy\n"
-            << mLayerHierarchyBuilder.getHierarchy() << "\nOffscreen Hierarchy\n"
-            << mLayerHierarchyBuilder.getOffscreenHierarchy() << "\n\n";
-        result = out.str();
-        dumpHwcLayersMinidump(result);
-    }
+    out << "\nLayer Hierarchy\n"
+        << mLayerHierarchyBuilder.getHierarchy() << "\nOffscreen Hierarchy\n"
+        << mLayerHierarchyBuilder.getOffscreenHierarchy() << "\n\n";
+    result = out.str();
+    dumpHwcLayersMinidump(result);
 }
 
 perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
@@ -6589,20 +5818,16 @@
         }
     }
 
-    if (mLegacyFrontEndEnabled) {
-        perfetto::protos::LayersProto layersProto;
-        for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
-            if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
-                continue;
-            }
-            layer->writeToProto(layersProto, traceFlags);
-        }
-        return layersProto;
+    auto traceGenerator =
+            LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
+                                            mLegacyLayers, traceFlags)
+                    .with(mLayerHierarchyBuilder.getHierarchy());
+
+    if (traceFlags & LayerTracing::Flag::TRACE_EXTRA) {
+        traceGenerator.withOffscreenLayers(mLayerHierarchyBuilder.getOffscreenHierarchy());
     }
 
-    return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
-                                           mLegacyLayers, traceFlags)
-            .generate(mLayerHierarchyBuilder.getHierarchy());
+    return traceGenerator.generate();
 }
 
 google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
@@ -6636,66 +5861,16 @@
     getHwComposer().dump(result);
 }
 
-void SurfaceFlinger::dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
-                                              uint32_t traceFlags) const {
-    // Add a fake invisible root layer to the proto output and parent all the offscreen layers to
-    // it.
-    perfetto::protos::LayerProto* rootProto = layersProto.add_layers();
-    const int32_t offscreenRootLayerId = INT32_MAX - 2;
-    rootProto->set_id(offscreenRootLayerId);
-    rootProto->set_name("Offscreen Root");
-    rootProto->set_parent(-1);
-
-    for (Layer* offscreenLayer : mOffscreenLayers) {
-        // Add layer as child of the fake root
-        rootProto->add_children(offscreenLayer->sequence);
-
-        // Add layer
-        auto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
-        layerProto->set_parent(offscreenRootLayerId);
-    }
-}
-
 perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
-    return mScheduler->schedule([=, this] { return dumpDrawingStateProto(traceFlags); }).get();
-}
-
-void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
-    auto future = mScheduler->schedule([this] {
-        std::string result;
-        for (Layer* offscreenLayer : mOffscreenLayers) {
-            offscreenLayer->traverse(LayerVector::StateSet::Drawing,
-                                     [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
-        }
-        return result;
-    });
-
-    result.append("Offscreen Layers:\n");
-    result.append(future.get());
-}
-
-void SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy(std::string& result) const {
-    for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
-        const auto displayId = HalDisplayId::tryCast(display->getId());
-        if (!displayId) {
-            continue;
-        }
-
-        StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(),
-                      displayId == mActiveDisplayId ? "active" : "inactive");
-        Layer::miniDumpHeader(result);
-
-        const DisplayDevice& ref = *display;
-        mDrawingState.traverseInZOrder([&](Layer* layer) { layer->miniDumpLegacy(result, ref); });
-        result.append("\n");
-    }
+    return mScheduler
+            ->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+                return dumpDrawingStateProto(traceFlags);
+            })
+            .get();
 }
 
 void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const {
-    if (!mLayerLifecycleManagerEnabled) {
-        return dumpHwcLayersMinidumpLockedLegacy(result);
-    }
-    for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+    for (const auto& [token, display] : mDisplays) {
         const auto displayId = HalDisplayId::tryCast(display->getId());
         if (!displayId) {
             continue;
@@ -6706,17 +5881,18 @@
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
-        mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
-            if (!snapshot.hasSomethingToDraw() ||
-                ref.getLayerStack() != snapshot.outputFilter.layerStack) {
-                return;
-            }
-            auto it = mLegacyLayers.find(snapshot.sequence);
-            LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                            "Couldnt find layer object for %s",
-                                            snapshot.getDebugString().c_str());
-            it->second->miniDump(result, snapshot, ref);
-        });
+        mLayerSnapshotBuilder.forEachVisibleSnapshot(
+                [&](const frontend::LayerSnapshot& snapshot) FTL_FAKE_GUARD(kMainThreadContext) {
+                    if (!snapshot.hasSomethingToDraw() ||
+                        ref.getLayerStack() != snapshot.outputFilter.layerStack) {
+                        return;
+                    }
+                    auto it = mLegacyLayers.find(snapshot.sequence);
+                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                    "Couldnt find layer object for %s",
+                                                    snapshot.getDebugString().c_str());
+                    it->second->miniDump(result, snapshot, ref);
+                });
         result.append("\n");
     }
 }
@@ -6773,8 +5949,7 @@
      * Dump the visible layer list
      */
     colorizer.bold(result);
-    StringAppendF(&result, "SurfaceFlinger New Frontend Enabled:%s\n",
-                  mLayerLifecycleManagerEnabled ? "true" : "false");
+    StringAppendF(&result, "SurfaceFlinger New Frontend Enabled:%s\n", "true");
     StringAppendF(&result, "Active Layers - layers with client handles (count = %zu)\n",
                   mNumLayers.load());
     colorizer.reset(result);
@@ -6813,24 +5988,23 @@
     StringAppendF(&result, "  transaction-flags         : %08x\n", mTransactionFlags.load());
 
     if (const auto display = getDefaultDisplayDeviceLocked()) {
-        std::string fps, xDpi, yDpi;
-        if (const auto activeModePtr =
-                    display->refreshRateSelector().getActiveMode().modePtr.get()) {
-            fps = to_string(activeModePtr->getVsyncRate());
-
+        std::string peakFps, xDpi, yDpi;
+        const auto activeMode = display->refreshRateSelector().getActiveMode();
+        if (const auto activeModePtr = activeMode.modePtr.get()) {
+            peakFps = to_string(activeMode.modePtr->getPeakFps());
             const auto dpi = activeModePtr->getDpi();
             xDpi = base::StringPrintf("%.2f", dpi.x);
             yDpi = base::StringPrintf("%.2f", dpi.y);
         } else {
-            fps = "unknown";
+            peakFps = "unknown";
             xDpi = "unknown";
             yDpi = "unknown";
         }
         StringAppendF(&result,
-                      "  refresh-rate              : %s\n"
+                      "  peak-refresh-rate         : %s\n"
                       "  x-dpi                     : %s\n"
                       "  y-dpi                     : %s\n",
-                      fps.c_str(), xDpi.c_str(), yDpi.c_str());
+                      peakFps.c_str(), xDpi.c_str(), yDpi.c_str());
     }
 
     StringAppendF(&result, "  transaction time: %f us\n", inTransactionDuration / 1000.0);
@@ -6844,10 +6018,6 @@
     }
     result.push_back('\n');
 
-    if (mLegacyFrontEndEnabled) {
-        dumpHwcLayersMinidumpLockedLegacy(result);
-    }
-
     {
         DumpArgs plannerArgs;
         plannerArgs.add(); // first argument is ignored
@@ -6878,15 +6048,6 @@
 
     result.append(mTimeStats->miniDump());
     result.append("\n");
-
-    result.append("Window Infos:\n");
-    auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
-    StringAppendF(&result, "  max send vsync id: %" PRId64 "\n",
-                  ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
-    StringAppendF(&result, "  max send delay (ns): %" PRId64 " ns\n",
-                  windowInfosDebug.maxSendDelayDuration);
-    StringAppendF(&result, "  unsent messages: %zu\n", windowInfosDebug.pendingMessageCount);
-    result.append("\n");
 }
 
 mat4 SurfaceFlinger::calculateColorMatrix(float saturation) {
@@ -6953,8 +6114,8 @@
         // Used by apps to hook Choreographer to SurfaceFlinger.
         case CREATE_DISPLAY_EVENT_CONNECTION:
         case CREATE_CONNECTION:
-        case CREATE_DISPLAY:
-        case DESTROY_DISPLAY:
+        case CREATE_VIRTUAL_DISPLAY:
+        case DESTROY_VIRTUAL_DISPLAY:
         case GET_PRIMARY_PHYSICAL_DISPLAY_ID:
         case GET_PHYSICAL_DISPLAY_IDS:
         case GET_PHYSICAL_DISPLAY_TOKEN:
@@ -7091,6 +6252,7 @@
                 Mutex::Autolock _l(mStateLock);
                 // daltonize
                 n = data.readInt32();
+                mDaltonizer.setLevel(data.readInt32());
                 switch (n % 10) {
                     case 1:
                         mDaltonizer.setType(ColorBlindnessType::Protanomaly);
@@ -7379,15 +6541,9 @@
                 return NO_ERROR;
             }
             case 1039: {
-                PhysicalDisplayId displayId = [&]() {
-                    Mutex::Autolock lock(mStateLock);
-                    return getDefaultDisplayDeviceLocked()->getPhysicalId();
-                }();
-
-                auto inUid = static_cast<uid_t>(data.readInt32());
+                const auto uid = static_cast<uid_t>(data.readInt32());
                 const auto refreshRate = data.readFloat();
-                mScheduler->setPreferredRefreshRateForUid(FrameRateOverride{inUid, refreshRate});
-                mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
+                mScheduler->setPreferredRefreshRateForUid(FrameRateOverride{uid, refreshRate});
                 return NO_ERROR;
             }
             // Toggle caching feature
@@ -7454,14 +6610,11 @@
                 auto future = mScheduler->schedule(
                         [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
                             n = data.readInt32();
-                            mHdrSdrRatioOverlay = n != 0;
-                            switch (n) {
-                                case 0:
-                                case 1:
-                                    enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
-                                    break;
-                                default:
-                                    reply->writeBool(isHdrSdrRatioOverlayEnabled());
+                            if (n == 0 || n == 1) {
+                                mHdrSdrRatioOverlay = n != 0;
+                                enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+                            } else {
+                                reply->writeBool(isHdrSdrRatioOverlayEnabled());
                             }
                         });
                 future.wait();
@@ -7579,6 +6732,25 @@
 
     // Update the overlay on the main thread to avoid race conditions with
     // RefreshRateSelector::getActiveMode
+    static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+        const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
+        if (!display) {
+            ALOGW("%s: default display is null", __func__);
+            return;
+        }
+        if (!display->isRefreshRateOverlayEnabled()) return;
+
+        const auto state = mDisplayModeController.getKernelIdleTimerState(display->getPhysicalId());
+
+        if (display->onKernelTimerChanged(state.desiredModeIdOpt, state.isEnabled && expired)) {
+            mScheduler->scheduleFrame();
+        }
+    }));
+}
+
+void SurfaceFlinger::vrrDisplayIdle(bool idle) {
+    // Update the overlay on the main thread to avoid race conditions with
+    // RefreshRateSelector::getActiveMode
     static_cast<void>(mScheduler->schedule([=, this] {
         const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
         if (!display) {
@@ -7587,27 +6759,18 @@
         }
         if (!display->isRefreshRateOverlayEnabled()) return;
 
-        const auto desiredModeIdOpt =
-                display->getDesiredMode().transform([](const display::DisplayModeRequest& request) {
-                    return request.mode.modePtr->getId();
-                });
-
-        const bool timerExpired = mKernelIdleTimerEnabled && expired;
-
-        if (display->onKernelTimerChanged(desiredModeIdOpt, timerExpired)) {
-            mScheduler->scheduleFrame();
-        }
+        display->onVrrIdle(idle);
+        mScheduler->scheduleFrame();
     }));
 }
 
-std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
-SurfaceFlinger::getKernelIdleTimerProperties(DisplayId displayId) {
+auto SurfaceFlinger::getKernelIdleTimerProperties(PhysicalDisplayId displayId)
+        -> std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds> {
     const bool isKernelIdleTimerHwcSupported = getHwComposer().getComposer()->isSupported(
             android::Hwc2::Composer::OptionalFeature::KernelIdleTimer);
     const auto timeout = getIdleTimerTimeout(displayId);
     if (isKernelIdleTimerHwcSupported) {
-        if (const auto id = PhysicalDisplayId::tryCast(displayId);
-            getHwComposer().hasDisplayIdleTimerCapability(*id)) {
+        if (getHwComposer().hasDisplayIdleTimerCapability(displayId)) {
             // In order to decide if we can use the HWC api for idle timer
             // we query DisplayCapability::DISPLAY_IDLE_TIMER directly on the composer
             // without relying on hasDisplayCapability.
@@ -7626,63 +6789,6 @@
     return {std::nullopt, timeout};
 }
 
-void SurfaceFlinger::updateKernelIdleTimer(std::chrono::milliseconds timeout,
-                                           KernelIdleTimerController controller,
-                                           PhysicalDisplayId displayId) {
-    switch (controller) {
-        case KernelIdleTimerController::HwcApi: {
-            getHwComposer().setIdleTimerEnabled(displayId, timeout);
-            break;
-        }
-        case KernelIdleTimerController::Sysprop: {
-            base::SetProperty(KERNEL_IDLE_TIMER_PROP, timeout > 0ms ? "true" : "false");
-            break;
-        }
-    }
-}
-
-void SurfaceFlinger::toggleKernelIdleTimer() {
-    using KernelIdleTimerAction = scheduler::RefreshRateSelector::KernelIdleTimerAction;
-
-    const auto display = getDefaultDisplayDeviceLocked();
-    if (!display) {
-        ALOGW("%s: default display is null", __func__);
-        return;
-    }
-
-    // If the support for kernel idle timer is disabled for the active display,
-    // don't do anything.
-    const std::optional<KernelIdleTimerController> kernelIdleTimerController =
-            display->refreshRateSelector().kernelIdleTimerController();
-    if (!kernelIdleTimerController.has_value()) {
-        return;
-    }
-
-    const KernelIdleTimerAction action = display->refreshRateSelector().getIdleTimerAction();
-
-    switch (action) {
-        case KernelIdleTimerAction::TurnOff:
-            if (mKernelIdleTimerEnabled) {
-                ATRACE_INT("KernelIdleTimer", 0);
-                std::chrono::milliseconds constexpr kTimerDisabledTimeout = 0ms;
-                updateKernelIdleTimer(kTimerDisabledTimeout, kernelIdleTimerController.value(),
-                                      display->getPhysicalId());
-                mKernelIdleTimerEnabled = false;
-            }
-            break;
-        case KernelIdleTimerAction::TurnOn:
-            if (!mKernelIdleTimerEnabled) {
-                ATRACE_INT("KernelIdleTimer", 1);
-                const std::chrono::milliseconds timeout =
-                        display->refreshRateSelector().getIdleTimerTimeout();
-                updateKernelIdleTimer(timeout, kernelIdleTimerController.value(),
-                                      display->getPhysicalId());
-                mKernelIdleTimerEnabled = true;
-            }
-            break;
-    }
-}
-
 // A simple RAII class to disconnect from an ANativeWindow* when it goes out of scope
 class WindowDisconnector {
 public:
@@ -7708,7 +6814,8 @@
     IPCThreadState* ipc = IPCThreadState::self();
     const int pid = ipc->getCallingPid();
     const int uid = ipc->getCallingUid();
-    if (uid == AID_GRAPHICS || PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) {
+    if (uid == AID_GRAPHICS || uid == AID_SYSTEM ||
+        PermissionCache::checkPermission(sReadFramebuffer, pid, uid)) {
         return OK;
     }
 
@@ -7768,14 +6875,13 @@
 
 namespace {
 
-ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace, const DisplayDevice* display,
+ui::Dataspace pickBestDataspace(ui::Dataspace requestedDataspace,
+                                const compositionengine::impl::OutputCompositionState& state,
                                 bool capturingHdrLayers, bool hintForSeamlessTransition) {
-    if (requestedDataspace != ui::Dataspace::UNKNOWN || display == nullptr) {
+    if (requestedDataspace != ui::Dataspace::UNKNOWN) {
         return requestedDataspace;
     }
 
-    const auto& state = display->getCompositionDisplay()->getState();
-
     const auto dataspaceForColorMode = ui::pickDataspaceFor(state.colorMode);
 
     // TODO: Enable once HDR screenshots are ready.
@@ -7802,21 +6908,24 @@
 
 void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
                                     const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
-    status_t validate = validateScreenshotPermissions(args);
+    const auto& captureArgs = args.captureArgs;
+    status_t validate = validateScreenshotPermissions(captureArgs);
     if (validate != OK) {
+        ALOGD("Permission denied to captureDisplay");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
 
     if (!args.displayToken) {
+        ALOGD("Invalid display token to captureDisplay");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+    if (captureArgs.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -7829,6 +6938,7 @@
         Mutex::Autolock lock(mStateLock);
         sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7840,37 +6950,35 @@
             reqSize = display->getLayerStackSpaceRect().getSize();
         }
 
-        for (const auto& handle : args.excludeHandles) {
+        for (const auto& handle : captureArgs.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");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureDisplay");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
         }
     }
 
-    RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, args.sourceCrop, reqSize, args.dataspace,
-                                         args.hintForSeamlessTransition, args.captureSecureLayers);
-    });
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(layerStack, captureArgs.uid,
+                                            std::move(excludeLayerIds));
 
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        getLayerSnapshots =
-                getLayerSnapshotsForScreenshots(layerStack, args.uid, std::move(excludeLayerIds));
-    } else {
-        auto traverseLayers = [this, args, excludeLayerIds,
-                               layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, args.uid, std::move(excludeLayerIds), visitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
-
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
-                        args.allowProtected, args.grayscale, captureListener);
+    ftl::Flags<RenderArea::Options> options;
+    if (captureArgs.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS;
+    if (captureArgs.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
+                                                 gui::aidl_utils::fromARect(captureArgs.sourceCrop),
+                                                 reqSize,
+                                                 static_cast<ui::Dataspace>(captureArgs.dataspace),
+                                                 displayWeak, options),
+                        getLayerSnapshotsFn, reqSize,
+                        static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
+                        captureArgs.allowProtected, captureArgs.grayscale,
+                        captureArgs.attachGainmap, captureListener);
 }
 
 void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
@@ -7883,6 +6991,7 @@
 
         const auto display = getDisplayDeviceLocked(displayId);
         if (!display) {
+            ALOGD("Unable to find display device for captureDisplay");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
@@ -7900,27 +7009,14 @@
     constexpr auto kMaxTextureSize = 16384;
     if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
         size.height >= kMaxTextureSize) {
-        ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+        ALOGD("captureDisplay resolved to invalid size %d x %d", size.width, size.height);
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    RenderAreaFuture renderAreaFuture = ftl::defer([=] {
-        return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
-                                         args.hintForSeamlessTransition,
-                                         false /* captureSecureLayers */);
-    });
-
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        getLayerSnapshots = getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
-                                                            /*snapshotFilterFn=*/nullptr);
-    } else {
-        auto traverseLayers = [this, layerStack](const LayerVector::Visitor& visitor) {
-            traverseLayersInLayerStack(layerStack, CaptureArgs::UNSET_UID, {}, visitor);
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
-    }
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(layerStack, CaptureArgs::UNSET_UID,
+                                            /*snapshotFilterFn=*/nullptr);
 
     if (captureListener == nullptr) {
         ALOGE("capture screen must provide a capture listener callback");
@@ -7931,8 +7027,15 @@
     constexpr bool kAllowProtected = false;
     constexpr bool kGrayscale = false;
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat,
-                        kAllowProtected, kGrayscale, captureListener);
+    ftl::Flags<RenderArea::Options> options;
+    if (args.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<DisplayRenderAreaBuilder>,
+                                                 Rect(), size,
+                                                 static_cast<ui::Dataspace>(args.dataspace),
+                                                 displayWeak, options),
+                        getLayerSnapshotsFn, size, static_cast<ui::PixelFormat>(args.pixelFormat),
+                        kAllowProtected, kGrayscale, args.attachGainmap, captureListener);
 }
 
 ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) {
@@ -7943,22 +7046,26 @@
 
 void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
                                    const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
-    status_t validate = validateScreenshotPermissions(args);
+    const auto& captureArgs = args.captureArgs;
+
+    status_t validate = validateScreenshotPermissions(captureArgs);
     if (validate != OK) {
+        ALOGD("Permission denied to captureLayers");
         invokeScreenCaptureError(validate, captureListener);
         return;
     }
 
+    auto crop = gui::aidl_utils::fromARect(captureArgs.sourceCrop);
+
     ui::Size reqSize;
     sp<Layer> parent;
-    Rect crop(args.sourceCrop);
     std::unordered_set<uint32_t> excludeLayerIds;
-    ui::Dataspace dataspace = args.dataspace;
+    ui::Dataspace dataspace = static_cast<ui::Dataspace>(captureArgs.dataspace);
 
-    if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
-        ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+    if (captureArgs.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+        ALOGD("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
         return;
     }
@@ -7968,36 +7075,38 @@
 
         parent = LayerHandle::getLayer(args.layerHandle);
         if (parent == nullptr) {
-            ALOGE("captureLayers called with an invalid or removed parent");
+            ALOGD("captureLayers called with an invalid or removed parent");
             invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
             return;
         }
 
         Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getDrawingState());
-        if (args.sourceCrop.width() <= 0) {
+        if (crop.width() <= 0) {
             crop.left = 0;
             crop.right = parentSourceBounds.getWidth();
         }
 
-        if (args.sourceCrop.height() <= 0) {
+        if (crop.height() <= 0) {
             crop.top = 0;
             crop.bottom = parentSourceBounds.getHeight();
         }
 
-        if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
+        if (crop.isEmpty() || captureArgs.frameScaleX <= 0.0f || captureArgs.frameScaleY <= 0.0f) {
             // Error out if the layer has no source bounds (i.e. they are boundless) and a source
             // crop was not specified, or an invalid frame scale was provided.
+            ALOGD("Boundless layer, unspecified crop, or invalid frame scale to captureLayers");
             invokeScreenCaptureError(BAD_VALUE, captureListener);
             return;
         }
-        reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
+        reqSize = ui::Size(crop.width() * captureArgs.frameScaleX,
+                           crop.height() * captureArgs.frameScaleY);
 
-        for (const auto& handle : args.excludeHandles) {
+        for (const auto& handle : captureArgs.excludeHandles) {
             uint32_t excludeLayer = LayerHandle::getLayerId(handle);
             if (excludeLayer != UNASSIGNED_LAYER_ID) {
                 excludeLayerIds.emplace(excludeLayer);
             } else {
-                ALOGW("Invalid layer handle passed as excludeLayer to captureLayers");
+                ALOGD("Invalid layer handle passed as excludeLayer to captureLayers");
                 invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
                 return;
             }
@@ -8006,86 +7115,92 @@
 
     // really small crop or frameScale
     if (reqSize.width <= 0 || reqSize.height <= 0) {
-        ALOGW("Failed to captureLayes: crop or scale too small");
+        ALOGD("Failed to captureLayers: crop or scale too small");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    bool childrenOnly = args.childrenOnly;
-    RenderAreaFuture renderAreaFuture = ftl::defer([=, this]() -> 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,
-                                                 layerTransform, layerBufferSize,
-                                                 args.hintForSeamlessTransition);
-    });
-    GetLayerSnapshotsFunction getLayerSnapshots;
-    if (mLayerLifecycleManagerEnabled) {
-        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);
-    } else {
-        auto traverseLayers = [parent, args, excludeLayerIds](const LayerVector::Visitor& visitor) {
-            parent->traverseChildrenInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
-                if (!layer->isVisible()) {
-                    return;
-                } else if (args.childrenOnly && layer == parent.get()) {
-                    return;
-                } else if (args.uid != CaptureArgs::UNSET_UID && args.uid != layer->getOwnerUid()) {
-                    return;
-                }
-
-                auto p = sp<Layer>::fromExisting(layer);
-                while (p != nullptr) {
-                    if (excludeLayerIds.count(p->sequence) != 0) {
-                        return;
-                    }
-                    p = p->getParent();
-                }
-
-                visitor(layer);
-            });
-        };
-        getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    std::optional<FloatRect> parentCrop = std::nullopt;
+    if (args.childrenOnly) {
+        parentCrop = crop.isEmpty() ? FloatRect(0, 0, reqSize.width, reqSize.height)
+                                    : crop.toFloatRect();
     }
 
+    GetLayerSnapshotsFunction getLayerSnapshotsFn =
+            getLayerSnapshotsForScreenshots(parent->sequence, captureArgs.uid,
+                                            std::move(excludeLayerIds), args.childrenOnly,
+                                            parentCrop);
+
     if (captureListener == nullptr) {
-        ALOGE("capture screen must provide a capture listener callback");
+        ALOGD("capture screen must provide a capture listener callback");
         invokeScreenCaptureError(BAD_VALUE, captureListener);
         return;
     }
 
-    captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
-                        args.allowProtected, args.grayscale, captureListener);
+    ftl::Flags<RenderArea::Options> options;
+    if (captureArgs.captureSecureLayers) options |= RenderArea::Options::CAPTURE_SECURE_LAYERS;
+    if (captureArgs.hintForSeamlessTransition)
+        options |= RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION;
+    captureScreenCommon(RenderAreaBuilderVariant(std::in_place_type<LayerRenderAreaBuilder>, crop,
+                                                 reqSize, dataspace, parent, args.childrenOnly,
+                                                 options),
+                        getLayerSnapshotsFn, reqSize,
+                        static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
+                        captureArgs.allowProtected, captureArgs.grayscale,
+                        captureArgs.attachGainmap, captureListener);
 }
 
-void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture,
-                                         GetLayerSnapshotsFunction getLayerSnapshots,
+// Creates a Future release fence for a layer and keeps track of it in a list to
+// release the buffer when the Future is complete. Calls from composittion
+// involve needing to refresh the composition start time for stats.
+void SurfaceFlinger::attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE,
+                                                     ui::LayerStack layerStack) {
+    ftl::Future<FenceResult> futureFence = layerFE->createReleaseFenceFuture();
+    layer->prepareReleaseCallbacks(std::move(futureFence), layerStack);
+}
+
+// Loop over all visible layers to see whether there's any protected layer. A protected layer is
+// typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
+// A protected layer has no implication on whether it's secure, which is explicitly set by
+// application to avoid being screenshot or drawn via unsecure display.
+bool SurfaceFlinger::layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const {
+    bool protectedLayerFound = false;
+    for (auto& layerFE : layers) {
+        protectedLayerFound |=
+                (layerFE->mSnapshot->isVisible && layerFE->mSnapshot->hasProtectedContent);
+        if (protectedLayerFound) {
+            break;
+        }
+    }
+    return protectedLayerFound;
+}
+
+// Getting layer snapshots and display should take place on main thread.
+// Accessing display requires mStateLock, and contention for this lock
+// is reduced when grabbed from the main thread, thus also reducing
+// risk of deadlocks.
+std::optional<SurfaceFlinger::OutputCompositionState> SurfaceFlinger::getSnapshotsFromMainThread(
+        RenderAreaBuilderVariant& renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
+        std::vector<sp<LayerFE>>& layerFEs) {
+    return mScheduler
+            ->schedule([=, this, &renderAreaBuilder, &layerFEs]() REQUIRES(kMainThreadContext) {
+                SFTRACE_NAME("getSnapshotsFromMainThread");
+                auto layers = getLayerSnapshotsFn();
+                for (auto& [layer, layerFE] : layers) {
+                    attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+                }
+                layerFEs = extractLayerFEs(layers);
+                return getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
+            })
+            .get();
+}
+
+void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder,
+                                         GetLayerSnapshotsFunction getLayerSnapshotsFn,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
-                                         bool allowProtected, bool grayscale,
+                                         bool allowProtected, bool grayscale, bool attachGainmap,
                                          const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (exceedsMaxRenderTargetSize(bufferSize.getWidth(), bufferSize.getHeight())) {
         ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
@@ -8095,95 +7210,292 @@
         return;
     }
 
-    // Loop over all visible layers to see whether there's any protected layer. A protected layer is
-    // typically a layer with DRM contents, or have the GRALLOC_USAGE_PROTECTED set on the buffer.
-    // A protected layer has no implication on whether it's secure, which is explicitly set by
-    // application to avoid being screenshot or drawn via unsecure display.
-    const bool supportsProtected = getRenderEngine().supportsProtectedContent();
-    bool hasProtectedLayer = false;
-    if (allowProtected && supportsProtected) {
-        hasProtectedLayer = mScheduler
-                                    ->schedule([=]() {
-                                        bool protectedLayerFound = false;
-                                        auto layers = getLayerSnapshots();
-                                        for (auto& [_, layerFe] : layers) {
-                                            protectedLayerFound |=
-                                                    (layerFe->mSnapshot->isVisible &&
-                                                     layerFe->mSnapshot->hasProtectedContent);
-                                        }
-                                        return protectedLayerFound;
-                                    })
-                                    .get();
-    }
-    const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
-    const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
-            GRALLOC_USAGE_HW_TEXTURE |
-            (isProtected ? GRALLOC_USAGE_PROTECTED
-                         : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
-    sp<GraphicBuffer> buffer =
-            getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
-                                             static_cast<android_pixel_format>(reqPixelFormat),
-                                             1 /* layerCount */, usage, "screenshot");
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) {
+        std::vector<sp<LayerFE>> layerFEs;
+        auto displayState =
+                getSnapshotsFromMainThread(renderAreaBuilder, getLayerSnapshotsFn, layerFEs);
 
-    const status_t bufferStatus = buffer->initCheck();
-    if (bufferStatus != OK) {
-        // Animations may end up being really janky, but don't crash here.
-        // Otherwise an irreponsible process may cause an SF crash by allocating
-        // too much.
-        ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
-        invokeScreenCaptureError(bufferStatus, captureListener);
-        return;
+        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
+        bool hasProtectedLayer = false;
+        if (allowProtected && supportsProtected) {
+            hasProtectedLayer = layersHasProtectedLayer(layerFEs);
+        }
+        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
+        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
+                GRALLOC_USAGE_HW_TEXTURE |
+                (isProtected ? GRALLOC_USAGE_PROTECTED
+                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+        sp<GraphicBuffer> buffer =
+                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
+                                                 static_cast<android_pixel_format>(reqPixelFormat),
+                                                 1 /* layerCount */, usage, "screenshot");
+
+        const status_t bufferStatus = buffer->initCheck();
+        if (bufferStatus != OK) {
+            // Animations may end up being really janky, but don't crash here.
+            // Otherwise an irreponsible process may cause an SF crash by allocating
+            // too much.
+            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
+            invokeScreenCaptureError(bufferStatus, captureListener);
+            return;
+        }
+        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
+        auto futureFence = captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */,
+                                             grayscale, isProtected, attachGainmap, captureListener,
+                                             displayState, layerFEs);
+        futureFence.get();
+
+    } else {
+        const bool supportsProtected = getRenderEngine().supportsProtectedContent();
+        bool hasProtectedLayer = false;
+        if (allowProtected && supportsProtected) {
+            auto layers = mScheduler->schedule([=]() { return getLayerSnapshotsFn(); }).get();
+            hasProtectedLayer = layersHasProtectedLayer(extractLayerFEs(layers));
+        }
+        const bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
+        const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
+                GRALLOC_USAGE_HW_TEXTURE |
+                (isProtected ? GRALLOC_USAGE_PROTECTED
+                             : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+        sp<GraphicBuffer> buffer =
+                getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
+                                                 static_cast<android_pixel_format>(reqPixelFormat),
+                                                 1 /* layerCount */, usage, "screenshot");
+
+        const status_t bufferStatus = buffer->initCheck();
+        if (bufferStatus != OK) {
+            // Animations may end up being really janky, but don't crash here.
+            // Otherwise an irreponsible process may cause an SF crash by allocating
+            // too much.
+            ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
+            invokeScreenCaptureError(bufferStatus, captureListener);
+            return;
+        }
+        const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
+                renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
+                                                     renderengine::impl::ExternalTexture::Usage::
+                                                             WRITEABLE);
+        auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture,
+                                                   false /* regionSampling */, grayscale,
+                                                   isProtected, attachGainmap, captureListener);
+        futureFence.get();
     }
-    const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
-            renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
-                                                 renderengine::impl::ExternalTexture::Usage::
-                                                         WRITEABLE);
-    auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
-                                     false /* regionSampling */, grayscale, isProtected,
-                                     captureListener);
-    fence.get();
 }
 
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
-        RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
+std::optional<SurfaceFlinger::OutputCompositionState>
+SurfaceFlinger::getDisplayStateFromRenderAreaBuilder(RenderAreaBuilderVariant& renderAreaBuilder) {
+    sp<const DisplayDevice> display = nullptr;
+    {
+        Mutex::Autolock lock(mStateLock);
+        if (auto* layerRenderAreaBuilder =
+                    std::get_if<LayerRenderAreaBuilder>(&renderAreaBuilder)) {
+            // LayerSnapshotBuilder should only be accessed from the main thread.
+            const frontend::LayerSnapshot* snapshot =
+                    mLayerSnapshotBuilder.getSnapshot(layerRenderAreaBuilder->layer->getSequence());
+            if (!snapshot) {
+                ALOGW("Couldn't find layer snapshot for %d",
+                      layerRenderAreaBuilder->layer->getSequence());
+            } else {
+                layerRenderAreaBuilder->setLayerSnapshot(*snapshot);
+                display = findDisplay(
+                        [layerStack = snapshot->outputFilter.layerStack](const auto& display) {
+                            return display.getLayerStack() == layerStack;
+                        });
+            }
+        } else if (auto* displayRenderAreaBuilder =
+                           std::get_if<DisplayRenderAreaBuilder>(&renderAreaBuilder)) {
+            display = displayRenderAreaBuilder->displayWeak.promote();
+        }
+
+        if (display == nullptr) {
+            display = getDefaultDisplayDeviceLocked();
+        }
+
+        if (display != nullptr) {
+            return std::optional{display->getCompositionDisplay()->getState()};
+        }
+    }
+    return std::nullopt;
+}
+
+std::vector<sp<LayerFE>> SurfaceFlinger::extractLayerFEs(
+        const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const {
+    std::vector<sp<LayerFE>> layerFEs;
+    layerFEs.reserve(layers.size());
+    for (const auto& [_, layerFE] : layers) {
+        layerFEs.push_back(layerFE);
+    }
+    return layerFEs;
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
+        const RenderAreaBuilderVariant& renderAreaBuilder,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
-    ATRACE_CALL();
+        bool grayscale, bool isProtected, bool attachGainmap,
+        const sp<IScreenCaptureListener>& captureListener,
+        std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
+    SFTRACE_CALL();
 
-    auto future = mScheduler->schedule(
-            [=, this, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
-                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
-                ScreenCaptureResults captureResults;
-                std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
-                if (!renderArea) {
-                    ALOGW("Skipping screen capture because of invalid render area.");
-                    if (captureListener) {
-                        captureResults.fenceResult = base::unexpected(NO_MEMORY);
-                        captureListener->onScreenCaptureCompleted(captureResults);
-                    }
-                    return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
-                }
+    ScreenCaptureResults captureResults;
+    std::unique_ptr<const RenderArea> renderArea =
+            std::visit([](auto&& arg) -> std::unique_ptr<RenderArea> { return arg.build(); },
+                       renderAreaBuilder);
 
-                ftl::SharedFuture<FenceResult> renderFuture;
-                renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-                    renderFuture =
-                            renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
-                                             grayscale, isProtected, captureResults);
-                });
+    if (!renderArea) {
+        ALOGW("Skipping screen capture because of invalid render area.");
+        if (captureListener) {
+            captureResults.fenceResult = base::unexpected(NO_MEMORY);
+            captureListener->onScreenCaptureCompleted(captureResults);
+        }
+        return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
+    }
+    float displayBrightnessNits = displayState.value().displayBrightnessNits;
+    float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
 
-                if (captureListener) {
-                    // Defer blocking on renderFuture back to the Binder thread.
-                    return ftl::Future(std::move(renderFuture))
-                            .then([captureListener, captureResults = std::move(captureResults)](
-                                          FenceResult fenceResult) mutable -> FenceResult {
-                                captureResults.fenceResult = std::move(fenceResult);
-                                captureListener->onScreenCaptureCompleted(captureResults);
-                                return base::unexpected(NO_ERROR);
+    // Empty vector needed to pass into renderScreenImpl for legacy path
+    std::vector<std::pair<Layer*, sp<android::LayerFE>>> layers;
+    ftl::SharedFuture<FenceResult> renderFuture =
+            renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
+                             attachGainmap, captureResults, displayState, layers, layerFEs);
+
+    if (captureResults.capturedHdrLayers && attachGainmap &&
+        FlagManager::getInstance().true_hdr_screenshots()) {
+        sp<GraphicBuffer> hdrBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 HAL_PIXEL_FORMAT_RGBA_FP16, 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-hdr");
+        sp<GraphicBuffer> gainmapBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 buffer->getPixelFormat(), 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-gainmap");
+
+        const status_t bufferStatus = hdrBuffer->initCheck();
+        const status_t gainmapBufferStatus = gainmapBuffer->initCheck();
+
+        if (bufferStatus != OK) {
+            ALOGW("%s: Buffer failed to allocate for hdr: %d. Screenshoting SDR instead.", __func__,
+                  bufferStatus);
+        } else if (gainmapBufferStatus != OK) {
+            ALOGW("%s: Buffer failed to allocate for gainmap: %d. Screenshoting SDR instead.",
+                  __func__, gainmapBufferStatus);
+        } else {
+            captureResults.optionalGainMap = gainmapBuffer;
+            const auto hdrTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(hdrBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+            const auto gainmapTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(gainmapBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+            ScreenCaptureResults unusedResults;
+            ftl::SharedFuture<FenceResult> hdrRenderFuture =
+                    renderScreenImpl(renderArea.get(), hdrTexture, regionSampling, grayscale,
+                                     isProtected, attachGainmap, unusedResults, displayState,
+                                     layers, layerFEs);
+
+            renderFuture =
+                    ftl::Future(std::move(renderFuture))
+                            .then([&, hdrRenderFuture = std::move(hdrRenderFuture),
+                                   displayBrightnessNits, sdrWhitePointNits,
+                                   dataspace = captureResults.capturedDataspace, buffer, hdrTexture,
+                                   gainmapTexture](FenceResult fenceResult) -> FenceResult {
+                                if (!fenceResult.ok()) {
+                                    return fenceResult;
+                                }
+
+                                auto hdrFenceResult = hdrRenderFuture.get();
+
+                                if (!hdrFenceResult.ok()) {
+                                    return hdrFenceResult;
+                                }
+
+                                return getRenderEngine()
+                                        .drawGainmap(buffer, fenceResult.value()->get(), hdrTexture,
+                                                     hdrFenceResult.value()->get(),
+                                                     displayBrightnessNits / sdrWhitePointNits,
+                                                     static_cast<ui::Dataspace>(dataspace),
+                                                     gainmapTexture)
+                                        .get();
                             })
                             .share();
-                }
-                return renderFuture;
-            });
+        };
+    }
+
+    if (captureListener) {
+        // Defer blocking on renderFuture back to the Binder thread.
+        return ftl::Future(std::move(renderFuture))
+                .then([captureListener, captureResults = std::move(captureResults),
+                       displayBrightnessNits,
+                       sdrWhitePointNits](FenceResult fenceResult) mutable -> FenceResult {
+                    captureResults.fenceResult = std::move(fenceResult);
+                    captureResults.hdrSdrRatio = displayBrightnessNits / sdrWhitePointNits;
+                    captureListener->onScreenCaptureCompleted(captureResults);
+                    return base::unexpected(NO_ERROR);
+                })
+                .share();
+    }
+    return renderFuture;
+}
+
+ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshotLegacy(
+        RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
+        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+        bool grayscale, bool isProtected, bool attachGainmap,
+        const sp<IScreenCaptureListener>& captureListener) {
+    SFTRACE_CALL();
+
+    auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
+                                    kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+        auto layers = getLayerSnapshotsFn();
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& [layer, layerFE] : layers) {
+                attachReleaseFenceFutureToLayer(layer, layerFE.get(), ui::INVALID_LAYER_STACK);
+            }
+        }
+        auto displayState = getDisplayStateFromRenderAreaBuilder(renderAreaBuilder);
+
+        ScreenCaptureResults captureResults;
+        std::unique_ptr<const RenderArea> renderArea =
+                std::visit([](auto&& arg) -> std::unique_ptr<RenderArea> { return arg.build(); },
+                           renderAreaBuilder);
+
+        if (!renderArea) {
+            ALOGW("Skipping screen capture because of invalid render area.");
+            if (captureListener) {
+                captureResults.fenceResult = base::unexpected(NO_MEMORY);
+                captureListener->onScreenCaptureCompleted(captureResults);
+            }
+            return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
+        }
+
+        auto layerFEs = extractLayerFEs(layers);
+        ftl::SharedFuture<FenceResult> renderFuture =
+                renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
+                                 attachGainmap, captureResults, displayState, layers, layerFEs);
+
+        if (captureListener) {
+            // Defer blocking on renderFuture back to the Binder thread.
+            return ftl::Future(std::move(renderFuture))
+                    .then([captureListener, captureResults = std::move(captureResults)](
+                                  FenceResult fenceResult) mutable -> FenceResult {
+                        captureResults.fenceResult = std::move(fenceResult);
+                        captureListener->onScreenCaptureCompleted(captureResults);
+                        return base::unexpected(NO_ERROR);
+                    })
+                    .share();
+        }
+        return renderFuture;
+    };
+
+    // TODO(b/294936197): Run takeScreenshotsFn() in a binder thread to reduce the number
+    // of calls on the main thread.
+    auto future =
+            mScheduler->schedule(FTL_FAKE_GUARD(kMainThreadContext, std::move(takeScreenshotFn)));
 
     // Flatten nested futures.
     auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture<FenceResult> future) {
@@ -8194,14 +7506,13 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults) {
-    ATRACE_CALL();
+        const RenderArea* renderArea, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+        bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
+        ScreenCaptureResults& captureResults, std::optional<OutputCompositionState>& displayState,
+        std::vector<std::pair<Layer*, sp<LayerFE>>>& layers, std::vector<sp<LayerFE>>& layerFEs) {
+    SFTRACE_CALL();
 
-    auto layers = getLayerSnapshots();
-
-    for (auto& [_, layerFE] : layers) {
+    for (auto& layerFE : layerFEs) {
         frontend::LayerSnapshot* snapshot = layerFE->mSnapshot.get();
         captureResults.capturedSecureLayers |= (snapshot->isVisible && snapshot->isSecure);
         captureResults.capturedHdrLayers |= isHdrLayer(*snapshot);
@@ -8221,79 +7532,64 @@
 
     captureResults.capturedDataspace = requestedDataspace;
 
-    {
-        Mutex::Autolock lock(mStateLock);
-        const DisplayDevice* display = nullptr;
-        if (parent) {
-            display = findDisplay([layerStack = parent->getLayerStack()](const auto& display) {
-                          return display.getLayerStack() == layerStack;
-                      }).get();
-        }
+    const bool enableLocalTonemapping = FlagManager::getInstance().local_tonemap_screenshots() &&
+            !renderArea->getHintForSeamlessTransition();
 
-        if (display == nullptr) {
-            display = renderArea->getDisplayDevice().get();
-        }
+    if (displayState) {
+        const auto& state = displayState.value();
+        captureResults.capturedDataspace =
+                pickBestDataspace(requestedDataspace, state, captureResults.capturedHdrLayers,
+                                  renderArea->getHintForSeamlessTransition());
+        sdrWhitePointNits = state.sdrWhitePointNits;
 
-        if (display == nullptr) {
-            display = getDefaultDisplayDeviceLocked().get();
-        }
-
-        if (display != nullptr) {
-            const auto& state = display->getCompositionDisplay()->getState();
-            captureResults.capturedDataspace =
-                    pickBestDataspace(requestedDataspace, display, captureResults.capturedHdrLayers,
-                                      renderArea->getHintForSeamlessTransition());
-            sdrWhitePointNits = state.sdrWhitePointNits;
-
-             // TODO(b/298219334): Clean this up once we verify this doesn't break anything
-             static constexpr bool kScreenshotsDontDim = true;
-
-            if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) {
-                displayBrightnessNits = sdrWhitePointNits;
-            } else {
-                displayBrightnessNits = state.displayBrightnessNits;
-                // Only clamp the display brightness if this is not a seamless transition. Otherwise
-                // for seamless transitions it's important to match the current display state as the
-                // buffer will be shown under these same conditions, and we want to avoid any
-                // flickers
+        if (!captureResults.capturedHdrLayers) {
+            displayBrightnessNits = sdrWhitePointNits;
+        } else {
+            displayBrightnessNits = state.displayBrightnessNits;
+            if (!enableLocalTonemapping) {
+                // Only clamp the display brightness if this is not a seamless transition.
+                // Otherwise for seamless transitions it's important to match the current
+                // display state as the buffer will be shown under these same conditions, and we
+                // want to avoid any flickers
                 if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
-                    // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
-                    // the SDR portion. 2.0 chosen by experimentation
+                    // Restrict the amount of HDR "headroom" in the screenshot to avoid
+                    // over-dimming the SDR portion. 2.0 chosen by experimentation
                     constexpr float kMaxScreenshotHeadroom = 2.0f;
                     displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
                                                      displayBrightnessNits);
                 }
             }
+        }
 
-            // Screenshots leaving the device should be colorimetric
-            if (requestedDataspace == ui::Dataspace::UNKNOWN &&
-                renderArea->getHintForSeamlessTransition()) {
-                renderIntent = state.renderIntent;
-            }
+        // Screenshots leaving the device should be colorimetric
+        if (requestedDataspace == ui::Dataspace::UNKNOWN &&
+            renderArea->getHintForSeamlessTransition()) {
+            renderIntent = state.renderIntent;
         }
     }
 
     captureResults.buffer = capturedBuffer->getBuffer();
 
     ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
-    if (!layers.empty()) {
-        const sp<LayerFE>& layerFE = layers.back().second;
+    if (!layerFEs.empty()) {
+        const sp<LayerFE>& layerFE = layerFEs.back();
         layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
     }
 
-    auto copyLayerFEs = [&layers]() {
-        std::vector<sp<compositionengine::LayerFE>> layerFEs;
-        layerFEs.reserve(layers.size());
-        for (const auto& [_, layerFE] : layers) {
-            layerFEs.push_back(layerFE);
+    auto copyLayerFEs = [&layerFEs]() {
+        std::vector<sp<compositionengine::LayerFE>> ceLayerFEs;
+        ceLayerFEs.reserve(layerFEs.size());
+        for (const auto& layerFE : layerFEs) {
+            ceLayerFEs.push_back(layerFE);
         }
-        return layerFEs;
+        return ceLayerFEs;
     };
 
     auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
                     sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected,
                     layerFEs = copyLayerFEs(), layerStack, regionSampling,
-                    renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+                    renderArea = std::move(renderArea), renderIntent,
+                    enableLocalTonemapping]() -> FenceResult {
         std::unique_ptr<compositionengine::CompositionEngine> compositionEngine =
                 mFactory.createCompositionEngine();
         compositionEngine->setRenderEngine(mRenderEngine.get());
@@ -8302,7 +7598,11 @@
                                                              .renderIntent = renderIntent};
 
         float targetBrightness = 1.0f;
-        if (dataspace == ui::Dataspace::BT2020_HLG) {
+        if (enableLocalTonemapping) {
+            // Boost the whole scene so that SDR white is at 1.0 while still communicating the hdr
+            // sdr ratio via display brightness / sdrWhite nits.
+            targetBrightness = sdrWhitePointNits / displayBrightnessNits;
+        } else if (dataspace == ui::Dataspace::BT2020_HLG) {
             const float maxBrightnessNits = displayBrightnessNits / sdrWhitePointNits * 203;
             // With a low dimming ratio, don't fit the entire curve. Otherwise mixed content
             // will appear way too bright.
@@ -8328,7 +7628,8 @@
                                         .treat170mAsSrgb = mTreat170mAsSrgb,
                                         .dimInGammaSpaceForEnhancedScreenshots =
                                                 dimInGammaSpaceForEnhancedScreenshots,
-                                        .isProtected = isProtected});
+                                        .isProtected = isProtected,
+                                        .enableLocalTonemapping = enableLocalTonemapping});
 
         const float colorSaturation = grayscale ? 0 : 1;
         compositionengine::CompositionRefreshArgs refreshArgs{
@@ -8350,92 +7651,48 @@
     //
     // TODO(b/196334700) Once we use RenderEngineThreaded everywhere we can always defer the call
     // to CompositionEngine::present.
-    auto presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
-                                                     : ftl::yield(present()).share();
+    ftl::SharedFuture<FenceResult> presentFuture;
+    if (FlagManager::getInstance().single_hop_screenshot() &&
+        FlagManager::getInstance().ce_fence_promise() && mRenderEngine->isThreaded()) {
+        presentFuture = ftl::yield(present()).share();
+    } else {
+        presentFuture = mRenderEngine->isThreaded() ? ftl::defer(std::move(present)).share()
+                                                    : ftl::yield(present()).share();
+    }
 
-    for (auto& [layer, layerFE] : layers) {
-        layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
-                                [layerFE = std::move(layerFE)](FenceResult) {
-                                    if (FlagManager::getInstance()
-                                                .screenshot_fence_preservation()) {
-                                        const auto compositionResult =
-                                                layerFE->stealCompositionResult();
-                                        const auto& fences = compositionResult.releaseFences;
-                                        // CompositionEngine may choose to cull layers that
-                                        // aren't visible, so pass a non-fence.
-                                        return fences.empty() ? Fence::NO_FENCE
-                                                              : fences.back().first.get();
-                                    } else {
-                                        return layerFE->stealCompositionResult()
-                                                .releaseFences.back()
-                                                .first.get();
-                                    }
-                                });
+    if (!FlagManager::getInstance().ce_fence_promise()) {
+        for (auto& [layer, layerFE] : layers) {
+            layer->onLayerDisplayed(presentFuture, ui::INVALID_LAYER_STACK,
+                                    [layerFE = std::move(layerFE)](FenceResult) {
+                                        if (FlagManager::getInstance()
+                                                    .screenshot_fence_preservation()) {
+                                            const auto compositionResult =
+                                                    layerFE->stealCompositionResult();
+                                            const auto& fences = compositionResult.releaseFences;
+                                            // CompositionEngine may choose to cull layers that
+                                            // aren't visible, so pass a non-fence.
+                                            return fences.empty() ? Fence::NO_FENCE
+                                                                  : fences.back().first.get();
+                                        } else {
+                                            return layerFE->stealCompositionResult()
+                                                    .releaseFences.back()
+                                                    .first.get();
+                                        }
+                                    });
+        }
     }
 
     return presentFuture;
 }
 
 void SurfaceFlinger::traverseLegacyLayers(const LayerVector::Visitor& visitor) const {
-    if (mLayerLifecycleManagerEnabled) {
-        for (auto& layer : mLegacyLayers) {
-            visitor(layer.second.get());
-        }
-    } else {
-        mDrawingState.traverse(visitor);
+    for (auto& layer : mLegacyLayers) {
+        visitor(layer.second.get());
     }
 }
 
 // ---------------------------------------------------------------------------
 
-void SurfaceFlinger::State::traverse(const LayerVector::Visitor& visitor) const {
-    layersSortedByZ.traverse(visitor);
-}
-
-void SurfaceFlinger::State::traverseInZOrder(const LayerVector::Visitor& visitor) const {
-    layersSortedByZ.traverseInZOrder(stateSet, visitor);
-}
-
-void SurfaceFlinger::State::traverseInReverseZOrder(const LayerVector::Visitor& visitor) const {
-    layersSortedByZ.traverseInReverseZOrder(stateSet, visitor);
-}
-
-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.
-    for (const auto& layer : mDrawingState.layersSortedByZ) {
-        if (layer->getLayerStack() != layerStack) {
-            continue;
-        }
-        // relative layers are traversed in Layer::traverseInZOrder
-        layer->traverseInZOrder(LayerVector::StateSet::Drawing, [&](Layer* layer) {
-            if (layer->isInternalDisplayOverlay()) {
-                return;
-            }
-            if (!layer->isVisible()) {
-                return;
-            }
-            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);
-        });
-    }
-}
-
 ftl::Optional<scheduler::FrameRateMode> SurfaceFlinger::getPreferredDisplayMode(
         PhysicalDisplayId displayId, DisplayModeId defaultModeId) const {
     if (const auto schedulerMode = mScheduler->getPreferredDisplayMode();
@@ -8457,7 +7714,7 @@
         const sp<DisplayDevice>& display,
         const scheduler::RefreshRateSelector::PolicyVariant& policy) {
     const auto displayId = display->getPhysicalId();
-    ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+    SFTRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
 
     Mutex::Autolock lock(mStateLock);
 
@@ -8478,45 +7735,17 @@
             break;
     }
 
-    if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
-        ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
-        return NO_ERROR;
-    }
-
     return applyRefreshRateSelectorPolicy(displayId, selector);
 }
 
-bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const {
-    if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true;
-
-    LOG_ALWAYS_FATAL_IF(display.isVirtual());
-    const auto displayId = display.getPhysicalId();
-
-    // The display is powered off, and this is a multi-display device. If the display is the
-    // inactive internal display of a dual-display foldable, then the policy will be applied
-    // when it becomes active upon powering on.
-    //
-    // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once
-    // concurrent mode setting across multiple (potentially powered off) displays is supported.
-    //
-    return displayId == mActiveDisplayId ||
-            !mPhysicalDisplays.get(displayId)
-                     .transform(&PhysicalDisplay::isInternal)
-                     .value_or(false);
-}
-
 status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
-        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) {
+        PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector) {
     const scheduler::RefreshRateSelector::Policy currentPolicy = selector.getCurrentPolicy();
     ALOGV("Setting desired display mode specs: %s", currentPolicy.toString().c_str());
 
-    // TODO(b/140204874): Leave the event in until we do proper testing with all apps that might
-    // be depending in this callback.
-    if (const auto activeMode = selector.getActiveMode(); displayId == mActiveDisplayId) {
-        mScheduler->onPrimaryDisplayModeChanged(scheduler::Cycle::Render, activeMode);
-        toggleKernelIdleTimer();
-    } else {
-        mScheduler->onNonPrimaryDisplayModeChanged(scheduler::Cycle::Render, activeMode);
+    if (const bool isPacesetter =
+                mScheduler->onDisplayModeChanged(displayId, selector.getActiveMode())) {
+        mDisplayModeController.updateKernelIdleTimer(displayId);
     }
 
     auto preferredModeOpt = getPreferredDisplayMode(displayId, currentPolicy.defaultMode);
@@ -8537,13 +7766,10 @@
         return INVALID_OPERATION;
     }
 
-    setDesiredMode({std::move(preferredMode), .emitEvent = true, .force = force});
+    setDesiredMode({std::move(preferredMode), .emitEvent = true});
 
     // Update the frameRateOverride list as the display render rate might have changed
-    if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
-        triggerOnFrameRateOverridesChanged();
-    }
-
+    mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps);
     return NO_ERROR;
 }
 
@@ -8574,7 +7800,7 @@
 
 status_t SurfaceFlinger::setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     const gui::DisplayModeSpecs& specs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken) {
         return BAD_VALUE;
@@ -8591,8 +7817,13 @@
             return INVALID_OPERATION;
         } else {
             using Policy = scheduler::RefreshRateSelector::DisplayManagerPolicy;
+            const auto idleScreenConfigOpt =
+                    FlagManager::getInstance().idle_screen_refresh_rate_timeout()
+                    ? specs.idleScreenRefreshRateConfig
+                    : std::nullopt;
             const Policy policy{DisplayModeId(specs.defaultMode), translate(specs.primaryRanges),
-                                translate(specs.appRequestRanges), specs.allowGroupSwitching};
+                                translate(specs.appRequestRanges), specs.allowGroupSwitching,
+                                idleScreenConfigOpt};
 
             return setDesiredDisplayModeSpecsInternal(display, policy);
         }
@@ -8603,7 +7834,7 @@
 
 status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
                                                     gui::DisplayModeSpecs* outSpecs) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     if (!displayToken || !outSpecs) {
         return BAD_VALUE;
@@ -8630,17 +7861,12 @@
 
 void SurfaceFlinger::onLayerFirstRef(Layer* layer) {
     mNumLayers++;
-    if (!layer->isRemovedFromCurrentState()) {
-        mScheduler->registerLayer(layer);
-    }
+    mScheduler->registerLayer(layer, scheduler::FrameRateCompatibility::Default);
 }
 
 void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
     mNumLayers--;
-    removeHierarchyFromOffscreenLayers(layer);
-    if (!layer->isRemovedFromCurrentState()) {
-        mScheduler->deregisterLayer(layer);
-    }
+    mScheduler->deregisterLayer(layer);
     if (mTransactionTracing) {
         mTransactionTracing->onLayerRemoved(layer->getSequence());
     }
@@ -8651,24 +7877,6 @@
     scheduleCommit(FrameHint::kActive);
 }
 
-// WARNING: ONLY CALL THIS FROM LAYER DTOR
-// Here we add children in the current state to offscreen layers and remove the
-// layer itself from the offscreen layer list.  Since
-// this is the dtor, it is safe to access the current state.  This keeps us
-// from dangling children layers such that they are not reachable from the
-// Drawing state nor the offscreen layer list
-// See b/141111965
-void SurfaceFlinger::removeHierarchyFromOffscreenLayers(Layer* layer) {
-    for (auto& child : layer->getCurrentChildren()) {
-        mOffscreenLayers.emplace(child.get());
-    }
-    mOffscreenLayers.erase(layer);
-}
-
-void SurfaceFlinger::removeFromOffscreenLayers(Layer* layer) {
-    mOffscreenLayers.erase(layer);
-}
-
 status_t SurfaceFlinger::setGlobalShadowSettings(const half4& ambientColor, const half4& spotColor,
                                                  float lightPosY, float lightPosZ,
                                                  float lightRadius) {
@@ -8698,13 +7906,7 @@
 }
 
 status_t SurfaceFlinger::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
-    PhysicalDisplayId displayId = [&]() {
-        Mutex::Autolock lock(mStateLock);
-        return getDefaultDisplayDeviceLocked()->getPhysicalId();
-    }();
-
-    mScheduler->setGameModeFrameRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
-    mScheduler->onFrameRateOverridesChanged(scheduler::Cycle::Render, displayId);
+    mScheduler->setGameModeFrameRateForUid(FrameRateOverride{uid, frameRate});
     return NO_ERROR;
 }
 
@@ -8729,22 +7931,29 @@
 
 void SurfaceFlinger::enableRefreshRateOverlay(bool enable) {
     bool setByHwc = getHwComposer().hasCapability(Capability::REFRESH_RATE_CHANGED_CALLBACK_DEBUG);
-    for (const auto& [id, display] : mPhysicalDisplays) {
-        if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
+    for (const auto& [displayId, physical] : mPhysicalDisplays) {
+        if (physical.snapshot().connectionType() == ui::DisplayConnectionType::Internal ||
             FlagManager::getInstance().refresh_rate_overlay_on_external_display()) {
-            if (const auto device = getDisplayDeviceLocked(id)) {
-                const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD(
-                                                   kMainThreadContext) {
-                    device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner,
-                                                     mRefreshRateOverlayRenderRate,
-                                                     mRefreshRateOverlayShowInMiddle);
+            if (const auto display = getDisplayDeviceLocked(displayId)) {
+                const auto enableOverlay = [&](bool setByHwc) FTL_FAKE_GUARD(kMainThreadContext) {
+                    const auto activeMode = mDisplayModeController.getActiveMode(displayId);
+                    const Fps refreshRate = activeMode.modePtr->getVsyncRate();
+                    const Fps renderFps = activeMode.fps;
+
+                    display->enableRefreshRateOverlay(enable, setByHwc, refreshRate, renderFps,
+                                                      mRefreshRateOverlaySpinner,
+                                                      mRefreshRateOverlayRenderRate,
+                                                      mRefreshRateOverlayShowInMiddle);
                 };
+
                 enableOverlay(setByHwc);
                 if (setByHwc) {
                     const auto status =
-                            getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable);
+                            getHwComposer().setRefreshRateChangedCallbackDebugEnabled(displayId,
+                                                                                      enable);
                     if (status != NO_ERROR) {
-                        ALOGE("Error updating the refresh rate changed callback debug enabled");
+                        ALOGE("Error %s refresh rate changed callback debug",
+                              enable ? "enabling" : "disabling");
                         enableOverlay(/*setByHwc*/ false);
                     }
                 }
@@ -8810,51 +8019,6 @@
     return calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
 }
 
-void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state, VsyncId vsyncId) {
-    sp<Layer> layer = state.layer.promote();
-    if (!layer) {
-        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;
-    if (state.initialParent != nullptr) {
-        parent = state.initialParent.promote();
-        if (parent == nullptr) {
-            ALOGD("Parent was destroyed soon after creation %p", state.initialParent.unsafe_get());
-            addToRoot = false;
-        }
-    }
-
-    if (parent == nullptr && addToRoot) {
-        layer->setIsAtRoot(true);
-        mCurrentState.layersSortedByZ.add(layer);
-    } else if (parent == nullptr) {
-        layer->onRemovedFromCurrentState();
-    } else if (parent->isRemovedFromCurrentState()) {
-        parent->addChild(layer);
-        layer->onRemovedFromCurrentState();
-    } else {
-        parent->addChild(layer);
-    }
-
-    ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
-    sp<const DisplayDevice> hintDisplay;
-    // Find the display that includes the layer.
-    for (const auto& [token, display] : mDisplays) {
-        if (display->getLayerStack() == layerStack) {
-            hintDisplay = display;
-            break;
-        }
-    }
-
-    if (hintDisplay) {
-        layer->updateTransformHint(hintDisplay->getTransformHint());
-    }
-}
-
 void SurfaceFlinger::sample() {
     if (!mLumaSampling || !mRegionSamplingThread) {
         return;
@@ -8888,22 +8052,15 @@
 
 void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr,
                                                   const DisplayDevice& activeDisplay) {
-    ATRACE_CALL();
-
-    // For the first display activated during boot, there is no need to force setDesiredMode,
-    // because DM is about to send its policy via setDesiredDisplayModeSpecs.
-    bool forceApplyPolicy = false;
+    SFTRACE_CALL();
 
     if (inactiveDisplayPtr) {
         inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
-        forceApplyPolicy = true;
     }
 
     mActiveDisplayId = activeDisplay.getPhysicalId();
     activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
 
-    mScheduler->resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
-
     // TODO(b/255635711): Check for pending mode changes on other displays.
     mScheduler->setModeChangePending(false);
 
@@ -8913,12 +8070,11 @@
     mActiveDisplayTransformHint = activeDisplay.getTransformHint();
     sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
 
-    // The policy of the new active/pacesetter display may have changed while it was inactive. In
-    // that case, its preferred mode has not been propagated to HWC (via setDesiredMode). In either
-    // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
-    // and the kernel idle timer of the newly active display must be toggled.
-    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
-                                   forceApplyPolicy);
+    // Whether or not the policy of the new active/pacesetter display changed while it was inactive
+    // (in which case its preferred mode has already been propagated to HWC via setDesiredMode), the
+    // Scheduler's cachedModeChangedParams must be initialized to the newly active mode, and the
+    // kernel idle timer of the newly active display must be toggled.
+    applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector());
 }
 
 status_t SurfaceFlinger::addWindowInfosListener(const sp<IWindowInfosListener>& windowInfosListener,
@@ -8936,6 +8092,8 @@
 
 status_t SurfaceFlinger::getStalledTransactionInfo(
         int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result) {
+    // Used to add a stalled transaction which uses an internal lock.
+    ftl::FakeGuard guard(kMainThreadContext);
     result = mTransactionHandler.getStalledTransactionInfo(pid);
     return NO_ERROR;
 }
@@ -9026,169 +8184,52 @@
     return nullptr;
 }
 
-bool SurfaceFlinger::commitMirrorDisplays(VsyncId vsyncId) {
-    std::vector<MirrorDisplayState> mirrorDisplays;
-    {
-        std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
-        mirrorDisplays = std::move(mMirrorDisplays);
-        mMirrorDisplays.clear();
-        if (mirrorDisplays.size() == 0) {
-            return false;
-        }
-    }
-
-    sp<IBinder> unused;
-    for (const auto& mirrorDisplay : mirrorDisplays) {
-        // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a display
-        // accidentally.
-        sp<Layer> rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle);
-        ssize_t idx = mCurrentState.layersSortedByZ.indexOf(rootMirrorLayer);
-        bool ret = rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
-        if (idx >= 0 && ret) {
-            mCurrentState.layersSortedByZ.removeAt(idx);
-            mCurrentState.layersSortedByZ.add(rootMirrorLayer);
-        }
-
-        for (const auto& layer : mDrawingState.layersSortedByZ) {
-            if (layer->getLayerStack() != mirrorDisplay.layerStack ||
-                layer->isInternalDisplayOverlay()) {
-                continue;
-            }
-
-            LayerCreationArgs mirrorArgs(this, mirrorDisplay.client, "MirrorLayerParent",
-                                         ISurfaceComposerClient::eNoColorFill,
-                                         gui::LayerMetadata());
-            sp<Layer> childMirror;
-            {
-                Mutex::Autolock lock(mStateLock);
-                createEffectLayer(mirrorArgs, &unused, &childMirror);
-                MUTEX_ALIAS(mStateLock, childMirror->mFlinger->mStateLock);
-                childMirror->setClonedChild(layer->createClone(childMirror->getSequence()));
-                childMirror->reparent(mirrorDisplay.rootHandle);
-            }
-            // lock on mStateLock needs to be released before binder handle gets destroyed
-            unused.clear();
-        }
-    }
-    return true;
-}
-
-bool SurfaceFlinger::commitCreatedLayers(VsyncId vsyncId,
-                                         std::vector<LayerCreatedState>& createdLayers) {
-    if (createdLayers.size() == 0) {
-        return false;
-    }
-
-    Mutex::Autolock _l(mStateLock);
-    for (const auto& createdLayer : createdLayers) {
-        handleLayerCreatedLocked(createdLayer, vsyncId);
-    }
-    mLayersAdded = true;
-    return mLayersAdded;
-}
-
-void SurfaceFlinger::updateLayerMetadataSnapshot() {
-    LayerMetadata parentMetadata;
-    for (const auto& layer : mDrawingState.layersSortedByZ) {
-        layer->updateMetadataSnapshot(parentMetadata);
-    }
-
-    std::unordered_set<Layer*> visited;
-    mDrawingState.traverse([&visited](Layer* layer) {
-        if (visited.find(layer) != visited.end()) {
-            return;
-        }
-
-        // If the layer isRelativeOf, then either it's relative metadata will be set
-        // recursively when updateRelativeMetadataSnapshot is called on its relative parent or
-        // it's relative parent has been deleted. Clear the layer's relativeLayerMetadata to ensure
-        // that layers with deleted relative parents don't hold stale relativeLayerMetadata.
-        if (layer->getDrawingState().isRelativeOf) {
-            layer->editLayerSnapshot()->relativeLayerMetadata = {};
-            return;
-        }
-
-        layer->updateRelativeMetadataSnapshot({}, visited);
-    });
-}
-
 void SurfaceFlinger::moveSnapshotsFromCompositionArgs(
         compositionengine::CompositionRefreshArgs& refreshArgs,
         const std::vector<std::pair<Layer*, LayerFE*>>& layers) {
-    if (mLayerLifecycleManagerEnabled) {
-        std::vector<std::unique_ptr<frontend::LayerSnapshot>>& snapshots =
-                mLayerSnapshotBuilder.getSnapshots();
-        for (auto [_, layerFE] : layers) {
-            auto i = layerFE->mSnapshot->globalZ;
-            snapshots[i] = std::move(layerFE->mSnapshot);
-        }
-    }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
-        for (auto [layer, layerFE] : layers) {
-            layer->updateLayerSnapshot(std::move(layerFE->mSnapshot));
-        }
+    std::vector<std::unique_ptr<frontend::LayerSnapshot>>& snapshots =
+            mLayerSnapshotBuilder.getSnapshots();
+    for (auto [_, layerFE] : layers) {
+        auto i = layerFE->mSnapshot->globalZ;
+        snapshots[i] = std::move(layerFE->mSnapshot);
     }
 }
 
 std::vector<std::pair<Layer*, LayerFE*>> SurfaceFlinger::moveSnapshotsToCompositionArgs(
         compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly) {
     std::vector<std::pair<Layer*, LayerFE*>> layers;
-    if (mLayerLifecycleManagerEnabled) {
-        nsecs_t currentTime = systemTime();
-        mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
-                    if (cursorOnly &&
-                        snapshot->compositionType !=
-                                aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
-                        return;
-                    }
-
-                    if (!snapshot->hasSomethingToDraw()) {
-                        return;
-                    }
-
-                    auto it = mLegacyLayers.find(snapshot->sequence);
-                    LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
-                                                    "Couldnt find layer object for %s",
-                                                    snapshot->getDebugString().c_str());
-                    auto& legacyLayer = it->second;
-                    sp<LayerFE> layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path);
-                    snapshot->fps = getLayerFramerate(currentTime, snapshot->sequence);
-                    layerFE->mSnapshot = std::move(snapshot);
-                    refreshArgs.layers.push_back(layerFE);
-                    layers.emplace_back(legacyLayer.get(), layerFE.get());
-                });
-    }
-    if (mLegacyFrontEndEnabled && !mLayerLifecycleManagerEnabled) {
-        auto moveSnapshots = [&layers, &refreshArgs, cursorOnly](Layer* layer) {
-            if (const auto& layerFE = layer->getCompositionEngineLayerFE()) {
+    nsecs_t currentTime = systemTime();
+    const bool needsMetadata = mCompositionEngine->getFeatureFlags().test(
+            compositionengine::Feature::kSnapshotLayerMetadata);
+    mLayerSnapshotBuilder.forEachSnapshot(
+            [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                    kMainThreadContext) {
                 if (cursorOnly &&
-                    layer->getLayerSnapshot()->compositionType !=
-                            aidl::android::hardware::graphics::composer3::Composition::CURSOR)
+                    snapshot->compositionType !=
+                            aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
                     return;
-                layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
-                layerFE->mSnapshot = layer->stealLayerSnapshot();
+                }
+
+                if (!snapshot->hasSomethingToDraw()) {
+                    return;
+                }
+
+                auto it = mLegacyLayers.find(snapshot->sequence);
+                LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+                                                "Couldnt find layer object for %s",
+                                                snapshot->getDebugString().c_str());
+                auto& legacyLayer = it->second;
+                sp<LayerFE> layerFE = legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+                snapshot->fps = getLayerFramerate(currentTime, snapshot->sequence);
+                layerFE->mSnapshot = std::move(snapshot);
                 refreshArgs.layers.push_back(layerFE);
-                layers.emplace_back(layer, layerFE.get());
-            }
-        };
-
-        if (cursorOnly || !mVisibleRegionsDirty) {
-            // for hot path avoid traversals by walking though the previous composition list
-            for (sp<Layer> layer : mPreviouslyComposedLayers) {
-                moveSnapshots(layer.get());
-            }
-        } else {
-            mPreviouslyComposedLayers.clear();
-            mDrawingState.traverseInZOrder(
-                    [&moveSnapshots](Layer* layer) { moveSnapshots(layer); });
-            mPreviouslyComposedLayers.reserve(layers.size());
-            for (auto [layer, _] : layers) {
-                mPreviouslyComposedLayers.push_back(sp<Layer>::fromExisting(layer));
-            }
-        }
-    }
-
+                layers.emplace_back(legacyLayer.get(), layerFE.get());
+            },
+            [needsMetadata](const frontend::LayerSnapshot& snapshot) {
+                return snapshot.isVisible ||
+                        (needsMetadata &&
+                         snapshot.changes.test(frontend::RequestedLayerState::Changes::Metadata));
+            });
     return layers;
 }
 
@@ -9197,11 +8238,12 @@
         std::optional<ui::LayerStack> layerStack, uint32_t uid,
         std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
                 snapshotFilterFn) {
-    return [&, layerStack, uid]() {
+    return [&, layerStack, uid]() FTL_FAKE_GUARD(kMainThreadContext) {
         std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
         bool stopTraversal = false;
         mLayerSnapshotBuilder.forEachVisibleSnapshot(
-                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) FTL_FAKE_GUARD(
+                        kMainThreadContext) {
                     if (stopTraversal) {
                         return;
                     }
@@ -9223,7 +8265,7 @@
                                                     "Couldnt find layer object for %s",
                                                     snapshot->getDebugString().c_str());
                     Layer* legacyLayer = (it == mLegacyLayers.end()) ? nullptr : it->second.get();
-                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name);
+                    sp<LayerFE> layerFE = getFactory().createLayerFE(snapshot->name, legacyLayer);
                     layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
                     layers.emplace_back(legacyLayer, std::move(layerFE));
                 });
@@ -9236,7 +8278,8 @@
 SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
                                                 uint32_t uid,
                                                 std::unordered_set<uint32_t> excludeLayerIds) {
-    return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() {
+    return [&, layerStack, uid,
+            excludeLayerIds = std::move(excludeLayerIds)]() FTL_FAKE_GUARD(kMainThreadContext) {
         if (excludeLayerIds.empty()) {
             auto getLayerSnapshotsFn =
                     getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
@@ -9278,7 +8321,7 @@
                                                 bool childrenOnly,
                                                 const std::optional<FloatRect>& parentCrop) {
     return [&, rootLayerId, uid, excludeLayerIds = std::move(excludeLayerIds), childrenOnly,
-            parentCrop]() {
+            parentCrop]() FTL_FAKE_GUARD(kMainThreadContext) {
         auto root = mLayerHierarchyBuilder.getPartialHierarchy(rootLayerId, childrenOnly);
         frontend::LayerSnapshotBuilder::Args
                 args{.root = root,
@@ -9313,33 +8356,6 @@
     };
 }
 
-frontend::Update SurfaceFlinger::flushLifecycleUpdates() {
-    frontend::Update update;
-    ATRACE_NAME("TransactionHandler:flushTransactions");
-    // Locking:
-    // 1. to prevent onHandleDestroyed from being called while the state lock is held,
-    // we must keep a copy of the transactions (specifically the composer
-    // states) around outside the scope of the lock.
-    // 2. Transactions and created layers do not share a lock. To prevent applying
-    // transactions with layers still in the createdLayer queue, flush the transactions
-    // before committing the created layers.
-    mTransactionHandler.collectTransactions();
-    update.transactions = mTransactionHandler.flushTransactions();
-    {
-        // TODO(b/238781169) lockless queue this and keep order.
-        std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
-        update.layerCreatedStates = std::move(mCreatedLayers);
-        mCreatedLayers.clear();
-        update.newLayers = std::move(mNewLayers);
-        mNewLayers.clear();
-        update.layerCreationArgs = std::move(mNewLayerArgs);
-        mNewLayerArgs.clear();
-        update.destroyedHandles = std::move(mDestroyedHandles);
-        mDestroyedHandles.clear();
-    }
-    return update;
-}
-
 void SurfaceFlinger::doActiveLayersTracingIfNeeded(bool isCompositionComputed,
                                                    bool visibleRegionDirty, TimePoint time,
                                                    VsyncId vsyncId) {
@@ -9361,7 +8377,7 @@
 
 perfetto::protos::LayersSnapshotProto SurfaceFlinger::takeLayersSnapshotProto(
         uint32_t traceFlags, TimePoint time, VsyncId vsyncId, bool visibleRegionDirty) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     perfetto::protos::LayersSnapshotProto snapshot;
     snapshot.set_elapsed_realtime_nanos(time.ns());
     snapshot.set_vsync_id(ftl::to_underlying(vsyncId));
@@ -9370,9 +8386,6 @@
                                             0);
 
     auto layers = dumpDrawingStateProto(traceFlags);
-    if (traceFlags & LayerTracing::Flag::TRACE_EXTRA) {
-        dumpOffscreenLayersProto(layers);
-    }
     *snapshot.mutable_layers() = std::move(layers);
 
     if (traceFlags & LayerTracing::Flag::TRACE_HWC) {
@@ -9458,35 +8471,35 @@
     }
 }
 
-binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
-                                                  float requestedRefreshRate,
-                                                  sp<IBinder>* outDisplay) {
+binder::Status SurfaceComposerAIDL::createVirtualDisplay(const std::string& displayName,
+                                                         bool isSecure, const std::string& uniqueId,
+                                                         float requestedRefreshRate,
+                                                         sp<IBinder>* outDisplay) {
     status_t status = checkAccessPermission();
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    String8 displayName8 = String8::format("%s", displayName.c_str());
-    *outDisplay = mFlinger->createDisplay(displayName8, secure, requestedRefreshRate);
+    *outDisplay =
+            mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId, requestedRefreshRate);
     return binder::Status::ok();
 }
 
-binder::Status SurfaceComposerAIDL::destroyDisplay(const sp<IBinder>& display) {
+binder::Status SurfaceComposerAIDL::destroyVirtualDisplay(const sp<IBinder>& displayToken) {
     status_t status = checkAccessPermission();
     if (status != OK) {
         return binderStatusFromStatusT(status);
     }
-    mFlinger->destroyDisplay(display);
-    return binder::Status::ok();
+    return binder::Status::fromStatusT(mFlinger->destroyVirtualDisplay(displayToken));
 }
 
 binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) {
     std::vector<PhysicalDisplayId> physicalDisplayIds = mFlinger->getPhysicalDisplayIds();
     std::vector<int64_t> displayIds;
     displayIds.reserve(physicalDisplayIds.size());
-    for (auto item : physicalDisplayIds) {
-        displayIds.push_back(static_cast<int64_t>(item.value));
+    for (const auto id : physicalDisplayIds) {
+        displayIds.push_back(static_cast<int64_t>(id.value));
     }
-    *outDisplayIds = displayIds;
+    *outDisplayIds = std::move(displayIds);
     return binder::Status::ok();
 }
 
@@ -9789,6 +8802,7 @@
         std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
         mFlinger->captureDisplay(*id, args, captureListener);
     } else {
+        ALOGD("Permission denied to captureDisplayById");
         invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
     }
     return binderStatusFromStatusT(NO_ERROR);
@@ -9834,22 +8848,6 @@
     return binderStatusFromStatusT(status);
 }
 
-binder::Status SurfaceComposerAIDL::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
-    if (!outLayers) {
-        return binderStatusFromStatusT(UNEXPECTED_NULL);
-    }
-
-    IPCThreadState* ipc = IPCThreadState::self();
-    const int pid = ipc->getCallingPid();
-    const int uid = ipc->getCallingUid();
-    if ((uid != AID_SHELL) && !PermissionCache::checkPermission(sDump, pid, uid)) {
-        ALOGE("Layer debug info permission denied for pid=%d, uid=%d", pid, uid);
-        return binderStatusFromStatusT(PERMISSION_DENIED);
-    }
-    status_t status = mFlinger->getLayerDebugInfo(outLayers);
-    return binderStatusFromStatusT(status);
-}
-
 binder::Status SurfaceComposerAIDL::getCompositionPreference(gui::CompositionPreference* outPref) {
     ui::Dataspace dataspace;
     ui::PixelFormat pixelFormat;
@@ -10261,6 +9259,33 @@
     return gui::getSchedulingPolicy(outPolicy);
 }
 
+binder::Status SurfaceComposerAIDL::notifyShutdown() {
+    TransactionTraceWriter::getInstance().invoke("systemShutdown_", /* overwrite= */ false);
+    return ::android::binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::addJankListener(const sp<IBinder>& layerHandle,
+                                                    const sp<gui::IJankListener>& listener) {
+    sp<Layer> layer = LayerHandle::getLayer(layerHandle);
+    if (layer == nullptr) {
+        return binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER);
+    }
+    JankTracker::addJankListener(layer->sequence, IInterface::asBinder(listener));
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::flushJankData(int32_t layerId) {
+    JankTracker::flushJankData(layerId);
+    return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::removeJankListener(int32_t layerId,
+                                                       const sp<gui::IJankListener>& listener,
+                                                       int64_t afterVsync) {
+    JankTracker::removeJankListener(layerId, IInterface::asBinder(listener), afterVsync);
+    return binder::Status::ok();
+}
+
 status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
     if (!mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(usePermissionCache)) {
         IPCThreadState* ipc = IPCThreadState::self();
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index afc7647..24194b1 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -27,7 +27,9 @@
 #include <android/gui/BnSurfaceComposer.h>
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
+#include <android/gui/IJankListener.h>
 #include <android/gui/ISurfaceComposerClient.h>
+#include <common/trace.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/algorithm.h>
@@ -38,7 +40,6 @@
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/ITransactionCompletedListener.h>
-#include <gui/LayerDebugInfo.h>
 #include <gui/LayerState.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
@@ -53,7 +54,6 @@
 #include <utils/KeyedVector.h>
 #include <utils/RefBase.h>
 #include <utils/SortedVector.h>
-#include <utils/Trace.h>
 #include <utils/threads.h>
 
 #include <compositionengine/OutputColorSetting.h>
@@ -66,6 +66,7 @@
 #include <ui/FenceResult.h>
 
 #include <common/FlagManager.h>
+#include "Display/DisplayModeController.h"
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
@@ -89,6 +90,7 @@
 #include "Tracing/TransactionTracing.h"
 #include "TransactionCallbackInvoker.h"
 #include "TransactionState.h"
+#include "Utils/OnceFuture.h"
 
 #include <atomic>
 #include <cstdint>
@@ -133,6 +135,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using ::aidl::android::hardware::drm::HdcpLevels;
 using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using frontend::TransactionHandler;
@@ -191,6 +194,9 @@
     Always,
 };
 
+struct DisplayRenderAreaBuilder;
+struct LayerRenderAreaBuilder;
+
 using DisplayColorSetting = compositionengine::OutputColorSetting;
 
 class SurfaceFlinger : public BnSurfaceComposer,
@@ -268,7 +274,7 @@
     enum class FrameHint { kNone, kActive };
 
     // Schedule commit of transactions on the main thread ahead of the next VSYNC.
-    void scheduleCommit(FrameHint);
+    void scheduleCommit(FrameHint, Duration workDurationSlack = Duration::fromNs(0));
     // As above, but also force composite regardless if transactions were committed.
     void scheduleComposite(FrameHint);
     // As above, but also force dirty geometry to repaint.
@@ -287,16 +293,11 @@
     void onLayerDestroyed(Layer*);
     void onLayerUpdate();
 
-    void removeHierarchyFromOffscreenLayers(Layer* layer);
-    void removeFromOffscreenLayers(Layer* layer);
-
     // Called when all clients have released all their references to
     // this layer. The layer may still be kept alive by its parents but
     // the client can no longer modify this layer directly.
     void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
 
-    std::vector<Layer*> mLayerMirrorRoots;
-
     TransactionCallbackInvoker& getTransactionCallbackInvoker() {
         return mTransactionCallbackInvoker;
     }
@@ -308,7 +309,6 @@
     // Disables expensive rendering for all displays
     // This is scheduled on the main thread
     void disableExpensiveRendering();
-    FloatRect getMaxDisplayBounds();
 
     // If set, composition engine tries to predict the composition strategy provided by HWC
     // based on the previous frame. If the strategy can be predicted, gpu composition will
@@ -382,17 +382,16 @@
 
     using TransactionSchedule = scheduler::TransactionSchedule;
     using GetLayerSnapshotsFunction = std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>;
-    using RenderAreaFuture = ftl::Future<std::unique_ptr<RenderArea>>;
+    using RenderAreaBuilderVariant = std::variant<DisplayRenderAreaBuilder, LayerRenderAreaBuilder>;
     using DumpArgs = Vector<String16>;
     using Dumper = std::function<void(const DumpArgs&, bool asProto, std::string&)>;
 
     class State {
     public:
-        explicit State(LayerVector::StateSet set) : stateSet(set), layersSortedByZ(set) {}
+        explicit State(LayerVector::StateSet set) : stateSet(set) {}
         State& operator=(const State& other) {
             // We explicitly don't copy stateSet so that, e.g., mDrawingState
             // always uses the Drawing StateSet.
-            layersSortedByZ = other.layersSortedByZ;
             displays = other.displays;
             colorMatrixChanged = other.colorMatrixChanged;
             if (colorMatrixChanged) {
@@ -404,7 +403,6 @@
         }
 
         const LayerVector::StateSet stateSet = LayerVector::StateSet::Invalid;
-        LayerVector layersSortedByZ;
 
         // TODO(b/241285876): Replace deprecated DefaultKeyedVector with ftl::SmallMap.
         DefaultKeyedVector<wp<IBinder>, DisplayDeviceState> displays;
@@ -424,10 +422,6 @@
         mat4 colorMatrix;
 
         ShadowSettings globalShadowSettings;
-
-        void traverse(const LayerVector::Visitor& visitor) const;
-        void traverseInZOrder(const LayerVector::Visitor& visitor) const;
-        void traverseInReverseZOrder(const LayerVector::Visitor& visitor) const;
     };
 
     // Keeps track of pending buffers per layer handle in the transaction queue or current/drawing
@@ -445,7 +439,7 @@
             if (it != mCounterByLayerHandle.end()) {
                 auto [name, pendingBuffers] = it->second;
                 int32_t count = ++(*pendingBuffers);
-                ATRACE_INT(name.c_str(), count);
+                SFTRACE_INT(name.c_str(), count);
             } else {
                 ALOGW("Handle not found! %p", layerHandle);
             }
@@ -508,10 +502,7 @@
         return lockedDumper(std::bind(dump, this, _1, _2, _3));
     }
 
-    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
-    Dumper mainThreadDumper(F dump) {
-        using namespace std::placeholders;
-        Dumper dumper = std::bind(dump, this, _3);
+    Dumper mainThreadDumperImpl(Dumper dumper) {
         return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
             mScheduler
                     ->schedule(
@@ -521,21 +512,35 @@
         };
     }
 
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper mainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _3));
+    }
+
+    template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+    Dumper argsMainThreadDumper(F dump) {
+        using namespace std::placeholders;
+        return mainThreadDumperImpl(std::bind(dump, this, _1, _3));
+    }
+
     // Maximum allowed number of display frames that can be set through backdoor
     static const int MAX_ALLOWED_DISPLAY_FRAMES = 2048;
 
     static const size_t MAX_LAYERS = 4096;
 
-    // Implements IBinder.
-    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
-    status_t dump(int fd, const Vector<String16>& args) override { return priorityDump(fd, args); }
-    bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true)
+    static bool callingThreadHasUnscopedSurfaceFlingerAccess(bool usePermissionCache = true)
             EXCLUDES(mStateLock);
 
-    // Implements ISurfaceComposer
-    sp<IBinder> createDisplay(const String8& displayName, bool secure,
-                              float requestedRefreshRate = 0.0f);
-    void destroyDisplay(const sp<IBinder>& displayToken);
+    // IBinder overrides:
+    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
+    status_t dump(int fd, const Vector<String16>& args) override { return priorityDump(fd, args); }
+
+    // ISurfaceComposer implementation:
+    sp<IBinder> createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                     const std::string& uniqueId,
+                                     float requestedRefreshRate = 0.0f);
+    status_t destroyVirtualDisplay(const sp<IBinder>& displayToken);
     std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
         Mutex::Autolock lock(mStateLock);
         return getPhysicalDisplayIdsLocked();
@@ -544,13 +549,13 @@
     sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
     status_t setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
             uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
     void bootFinished();
-    virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
+    status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
     sp<IDisplayEventConnection> createDisplayEventConnection(
             gui::ISurfaceComposer::VsyncSource vsyncSource =
                     gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
@@ -589,7 +594,6 @@
     status_t overrideHdrTypes(const sp<IBinder>& displayToken,
                               const std::vector<ui::Hdr>& hdrTypes);
     status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success);
-    status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers);
     status_t getCompositionPreference(ui::Dataspace* outDataspace, ui::PixelFormat* outPixelFormat,
                                       ui::Dataspace* outWideColorGamutDataspace,
                                       ui::PixelFormat* outWideColorGamutPixelFormat) const;
@@ -655,7 +659,7 @@
 
     void updateHdcpLevels(hal::HWDisplayId hwcDisplayId, int32_t connectedLevel, int32_t maxLevel);
 
-    // Implements IBinder::DeathRecipient.
+    // IBinder::DeathRecipient overrides:
     void binderDied(const wp<IBinder>& who) override;
 
     // HWC2::ComposerCallback overrides:
@@ -668,6 +672,7 @@
     void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
     void onComposerHalVsyncIdle(hal::HWDisplayId) override;
     void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override;
+    void onComposerHalHdcpLevelsChanged(hal::HWDisplayId, const HdcpLevels& levels) override;
 
     // ICompositor overrides:
     void configure() override REQUIRES(kMainThreadContext);
@@ -683,30 +688,23 @@
     void requestHardwareVsync(PhysicalDisplayId, bool) override;
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
     void kernelTimerChanged(bool expired) override;
-    void triggerOnFrameRateOverridesChanged() override;
     void onChoreographerAttached() override;
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
                                      Fps renderRate) override;
+    void onCommitNotComposited() override
+            REQUIRES(kMainThreadContext);
+    void vrrDisplayIdle(bool idle) override;
 
     // ICEPowerCallback overrides:
     void notifyCpuLoadUp() override;
 
-    // Toggles the kernel idle timer on or off depending the policy decisions around refresh rates.
-    void toggleKernelIdleTimer() REQUIRES(mStateLock);
-
     using KernelIdleTimerController = scheduler::RefreshRateSelector::KernelIdleTimerController;
 
     // Get the controller and timeout that will help decide how the kernel idle timer will be
     // configured and what value to use as the timeout.
     std::pair<std::optional<KernelIdleTimerController>, std::chrono::milliseconds>
-            getKernelIdleTimerProperties(DisplayId) REQUIRES(mStateLock);
-    // Updates the kernel idle timer either through HWC or through sysprop
-    // depending on which controller is provided
-    void updateKernelIdleTimer(std::chrono::milliseconds timeoutMs, KernelIdleTimerController,
-                               PhysicalDisplayId) REQUIRES(mStateLock);
-    // Keeps track of whether the kernel idle timer is currently enabled, so we don't have to
-    // make calls to sys prop each time.
-    bool mKernelIdleTimerEnabled = false;
+            getKernelIdleTimerProperties(PhysicalDisplayId) REQUIRES(mStateLock);
+
     // Show spinner with refresh rate overlay
     bool mRefreshRateOverlaySpinner = false;
     // Show render rate with refresh rate overlay
@@ -721,12 +719,12 @@
     status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
                                        Fps maxFps);
 
-    void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
-    void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
+    void initiateDisplayModeChanges() REQUIRES(kMainThreadContext) REQUIRES(mStateLock);
+    void finalizeDisplayModeChange(PhysicalDisplayId) REQUIRES(kMainThreadContext)
+            REQUIRES(mStateLock);
 
-    // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler.
-    void dropModeRequest(const sp<DisplayDevice>&) REQUIRES(mStateLock);
-    void applyActiveMode(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+    void dropModeRequest(PhysicalDisplayId) REQUIRES(kMainThreadContext);
+    void applyActiveMode(PhysicalDisplayId) REQUIRES(kMainThreadContext);
 
     // Called on the main thread in response to setPowerMode()
     void setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode)
@@ -741,45 +739,34 @@
             const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&)
             EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
 
-    bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const
-            REQUIRES(mStateLock, kMainThreadContext);
-
     // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
     status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
-                                            const scheduler::RefreshRateSelector&,
-                                            bool force = false)
+                                            const scheduler::RefreshRateSelector&)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
     void commitTransactions() REQUIRES(kMainThreadContext, mStateLock);
     void commitTransactionsLocked(uint32_t transactionFlags)
             REQUIRES(mStateLock, kMainThreadContext);
     void doCommitTransactions() REQUIRES(mStateLock);
 
-    // Returns whether a new buffer has been latched.
-    bool latchBuffers();
-
-    void updateLayerGeometry();
-    void updateLayerMetadataSnapshot();
     std::vector<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
-            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
+            compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly)
+            REQUIRES(kMainThreadContext);
     void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
-                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers);
-    // Return true if we must composite this frame
-    bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
-                                    bool& out) REQUIRES(kMainThreadContext);
+                                          const std::vector<std::pair<Layer*, LayerFE*>>& layers)
+            REQUIRES(kMainThreadContext);
     // Return true if we must composite this frame
     bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
                               bool& out) REQUIRES(kMainThreadContext);
-    void updateLayerHistory(nsecs_t now);
-    frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
+    void updateLayerHistory(nsecs_t now) REQUIRES(kMainThreadContext);
 
-    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
+    void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime) REQUIRES(kMainThreadContext);
     void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
     void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
-                          std::vector<gui::DisplayInfo>& outDisplayInfos);
+                          std::vector<gui::DisplayInfo>& outDisplayInfos)
+            REQUIRES(kMainThreadContext);
     void commitInputWindowCommands() REQUIRES(mStateLock);
-    void updateCursorAsync();
+    void updateCursorAsync() REQUIRES(kMainThreadContext);
 
     void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
 
@@ -795,35 +782,29 @@
                                const int64_t postTime, bool hasListenerCallbacks,
                                const std::vector<ListenerCallbacks>& listenerCallbacks,
                                int originPid, int originUid, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     // Flush pending transactions that were presented after desiredPresentTime.
     // For test only
-    bool flushTransactionQueues(VsyncId) REQUIRES(kMainThreadContext);
+    bool flushTransactionQueues() REQUIRES(kMainThreadContext);
 
-    bool applyTransactions(std::vector<TransactionState>&, VsyncId) REQUIRES(kMainThreadContext);
+    bool applyTransactions(std::vector<TransactionState>&) REQUIRES(kMainThreadContext);
     bool applyAndCommitDisplayTransactionStatesLocked(std::vector<TransactionState>& transactions)
             REQUIRES(kMainThreadContext, mStateLock);
 
     // Returns true if there is at least one transaction that needs to be flushed
-    bool transactionFlushNeeded();
-    void addTransactionReadyFilters();
+    bool transactionFlushNeeded() REQUIRES(kMainThreadContext);
+    void addTransactionReadyFilters() REQUIRES(kMainThreadContext);
     TransactionHandler::TransactionReadiness transactionReadyTimelineCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
-    TransactionHandler::TransactionReadiness transactionReadyBufferCheckLegacy(
-            const TransactionHandler::TransactionFlushState& flushState)
-            REQUIRES(kMainThreadContext);
     TransactionHandler::TransactionReadiness transactionReadyBufferCheck(
             const TransactionHandler::TransactionFlushState& flushState)
             REQUIRES(kMainThreadContext);
 
-    uint32_t setClientStateLocked(const FrameTimelineInfo&, ResolvedComposerState&,
-                                  int64_t desiredPresentTime, bool isAutoTimestamp,
-                                  int64_t postTime, uint64_t transactionId) REQUIRES(mStateLock);
     uint32_t updateLayerCallbacksAndStats(const FrameTimelineInfo&, ResolvedComposerState&,
                                           int64_t desiredPresentTime, bool isAutoTimestamp,
                                           int64_t postTime, uint64_t transactionId)
-            REQUIRES(mStateLock);
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t getTransactionFlags() const;
 
     // Sets the masked bits, and schedules a commit if needed.
@@ -834,12 +815,10 @@
     // Clears and returns the masked bits.
     uint32_t clearTransactionFlags(uint32_t mask);
 
-    void commitOffscreenLayers();
-
     static LatchUnsignaledConfig getLatchUnsignaledConfig();
     bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
-    bool applyTransactionsLocked(std::vector<TransactionState>& transactions, VsyncId)
-            REQUIRES(mStateLock);
+    bool applyTransactionsLocked(std::vector<TransactionState>& transactions)
+            REQUIRES(mStateLock, kMainThreadContext);
     uint32_t setDisplayStateLocked(const DisplayState& s) REQUIRES(mStateLock);
     uint32_t addInputWindowCommands(const InputWindowCommands& inputWindowCommands)
             REQUIRES(mStateLock);
@@ -856,43 +835,66 @@
     status_t createEffectLayer(const LayerCreationArgs& args, sp<IBinder>* outHandle,
                                sp<Layer>* outLayer);
 
+    // Checks if there are layer leaks before creating layer
+    status_t checkLayerLeaks();
+
     status_t mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
                          gui::CreateSurfaceResult& outResult);
 
     status_t mirrorDisplay(DisplayId displayId, const LayerCreationArgs& args,
                            gui::CreateSurfaceResult& outResult);
 
-    void markLayerPendingRemovalLocked(const sp<Layer>& layer) REQUIRES(mStateLock);
-
     // add a layer to SurfaceFlinger
     status_t addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
                             const sp<Layer>& layer, const wp<Layer>& parentLayer,
                             uint32_t* outTransformHint);
 
-    // Traverse through all the layers and compute and cache its bounds.
-    void computeLayerBounds();
+    // Creates a promise for a future release fence for a layer. This allows for
+    // the layer to keep track of when its buffer can be released.
+    void attachReleaseFenceFutureToLayer(Layer* layer, LayerFE* layerFE, ui::LayerStack layerStack);
 
-    // Boot animation, on/off animations and screen capture
-    void startBootAnim();
+    // Checks if a protected layer exists in a list of layers.
+    bool layersHasProtectedLayer(const std::vector<sp<LayerFE>>& layers) const;
 
-    void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
-                             ui::PixelFormat, bool allowProtected, bool grayscale,
-                             const sp<IScreenCaptureListener>&);
-    ftl::SharedFuture<FenceResult> captureScreenCommon(
-            RenderAreaFuture, GetLayerSnapshotsFunction,
+    using OutputCompositionState = compositionengine::impl::OutputCompositionState;
+
+    std::optional<OutputCompositionState> getSnapshotsFromMainThread(
+            RenderAreaBuilderVariant& renderAreaBuilder,
+            GetLayerSnapshotsFunction getLayerSnapshotsFn, std::vector<sp<LayerFE>>& layerFEs);
+
+    void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
+                             ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
+                             bool grayscale, bool attachGainmap, const sp<IScreenCaptureListener>&);
+
+    std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
+            RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
+
+    // Legacy layer raw pointer is not safe to access outside the main thread.
+    // Creates a new vector consisting only of LayerFEs, which can be safely
+    // accessed outside the main thread.
+    std::vector<sp<LayerFE>> extractLayerFEs(
+            const std::vector<std::pair<Layer*, sp<LayerFE>>>& layers) const;
+
+    ftl::SharedFuture<FenceResult> captureScreenshot(
+            const RenderAreaBuilderVariant& renderAreaBuilder,
+            const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+            bool grayscale, bool isProtected, bool attachGainmap,
+            const sp<IScreenCaptureListener>& captureListener,
+            std::optional<OutputCompositionState>& displayState,
+            std::vector<sp<LayerFE>>& layerFEs);
+
+    ftl::SharedFuture<FenceResult> captureScreenshotLegacy(
+            RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
+            bool grayscale, bool isProtected, bool attachGainmap,
+            const sp<IScreenCaptureListener>&);
+
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, ScreenCaptureResults&) EXCLUDES(mStateLock)
-            REQUIRES(kMainThreadContext);
-
-    // 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,
-                                    std::unordered_set<uint32_t> excludeLayerIds,
-                                    const LayerVector::Visitor&);
+            const RenderArea*, const std::shared_ptr<renderengine::ExternalTexture>&,
+            bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
+            ScreenCaptureResults&, std::optional<OutputCompositionState>& displayState,
+            std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
+            std::vector<sp<LayerFE>>& layerFEs);
 
     void readPersistentProperties();
 
@@ -942,18 +944,12 @@
         return nullptr;
     }
 
-    // Returns the primary display or (for foldables) the active display, assuming that the inner
-    // and outer displays have mutually exclusive power states.
+    // Returns the primary display or (for foldables) the active display.
     sp<const DisplayDevice> getDefaultDisplayDeviceLocked() const REQUIRES(mStateLock) {
         return const_cast<SurfaceFlinger*>(this)->getDefaultDisplayDeviceLocked();
     }
 
     sp<DisplayDevice> getDefaultDisplayDeviceLocked() REQUIRES(mStateLock) {
-        if (const auto display = getDisplayDeviceLocked(mActiveDisplayId)) {
-            return display;
-        }
-        // The active display is outdated, so fall back to the primary display.
-        mActiveDisplayId = getPrimaryDisplayIdLocked();
         return getDisplayDeviceLocked(mActiveDisplayId);
     }
 
@@ -962,8 +958,7 @@
         return getDefaultDisplayDeviceLocked();
     }
 
-    using DisplayDeviceAndSnapshot =
-            std::pair<sp<DisplayDevice>, display::PhysicalDisplay::SnapshotRef>;
+    using DisplayDeviceAndSnapshot = std::pair<sp<DisplayDevice>, display::DisplaySnapshotRef>;
 
     // Combinator for ftl::Optional<PhysicalDisplay>::and_then.
     auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) {
@@ -1032,10 +1027,13 @@
     bool configureLocked() REQUIRES(mStateLock) REQUIRES(kMainThreadContext)
             EXCLUDES(mHotplugMutex);
 
-    // Returns a string describing the hotplug, or nullptr if it was rejected.
-    const char* processHotplug(PhysicalDisplayId, hal::HWDisplayId, bool connected,
-                               DisplayIdentificationInfo&&) REQUIRES(mStateLock)
-            REQUIRES(kMainThreadContext);
+    // Returns the active mode ID, or nullopt on hotplug failure.
+    std::optional<DisplayModeId> processHotplugConnect(PhysicalDisplayId, hal::HWDisplayId,
+                                                       DisplayIdentificationInfo&&,
+                                                       const char* displayString)
+            REQUIRES(mStateLock, kMainThreadContext);
+    void processHotplugDisconnect(PhysicalDisplayId, const char* displayString)
+            REQUIRES(mStateLock, kMainThreadContext);
 
     sp<DisplayDevice> setupNewDisplayDeviceInternal(
             const wp<IBinder>& displayToken,
@@ -1051,14 +1049,6 @@
                                const DisplayDeviceState& drawingState)
             REQUIRES(mStateLock, kMainThreadContext);
 
-    void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&)
-            REQUIRES(mStateLock);
-
-    /*
-     * VSYNC
-     */
-    nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
-
     /*
      * Display identification
      */
@@ -1109,12 +1099,12 @@
     void dumpAll(const DumpArgs& args, const std::string& compositionLayers,
                  std::string& result) const EXCLUDES(mStateLock);
     void dumpHwcLayersMinidump(std::string& result) const REQUIRES(mStateLock, kMainThreadContext);
-    void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
 
     void appendSfConfigString(std::string& result) const;
-    void listLayersLocked(std::string& result) const;
-    void dumpStatsLocked(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
-    void clearStatsLocked(const DumpArgs& args, std::string& result);
+    void listLayers(std::string& result) const REQUIRES(kMainThreadContext);
+    void dumpStats(const DumpArgs& args, std::string& result) const
+            REQUIRES(mStateLock, kMainThreadContext);
+    void clearStats(const DumpArgs& args, std::string& result) REQUIRES(kMainThreadContext);
     void dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const;
     void dumpFrameTimeline(const DumpArgs& args, std::string& result) const;
     void logFrameStats(TimePoint now) REQUIRES(kMainThreadContext);
@@ -1132,9 +1122,8 @@
     void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext);
     void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext);
 
-    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
-    void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
-                                  uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
+    perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const
+            REQUIRES(kMainThreadContext);
     google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
     void doActiveLayersTracingIfNeeded(bool isCompositionComputed, bool visibleRegionDirty,
                                        TimePoint, VsyncId) REQUIRES(kMainThreadContext);
@@ -1146,7 +1135,6 @@
     void dumpHwc(std::string& result) const;
     perfetto::protos::LayersProto dumpProtoFromMainThread(
             uint32_t traceFlags = LayerTracing::TRACE_ALL) EXCLUDES(mStateLock);
-    void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
     void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
 
     status_t doDump(int fd, const DumpArgs& args, bool asProto);
@@ -1180,12 +1168,19 @@
 
     ui::Rotation getPhysicalDisplayOrientation(DisplayId, bool isPrimary) const
             REQUIRES(mStateLock);
-    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const;
+    void traverseLegacyLayers(const LayerVector::Visitor& visitor) const
+            REQUIRES(kMainThreadContext);
+
+    void initBootProperties();
     void initTransactionTraceWriter();
-    sp<StartPropertySetThread> mStartPropertySetThread;
+
     surfaceflinger::Factory& mFactory;
     pid_t mPid;
-    std::future<void> mRenderEnginePrimeCacheFuture;
+
+    // TODO: b/328459745 - Encapsulate in a SystemProperties object.
+    utils::OnceFuture mInitBootPropsFuture;
+
+    utils::OnceFuture mRenderEnginePrimeCacheFuture;
 
     // mStateLock has conventions related to the current thread, because only
     // the main thread should modify variables protected by mStateLock.
@@ -1200,7 +1195,6 @@
     State mCurrentState{LayerVector::StateSet::Current};
     std::atomic<int32_t> mTransactionFlags = 0;
     std::atomic<uint32_t> mUniqueTransactionId = 1;
-    SortedVector<sp<Layer>> mLayersPendingRemoval;
 
     // Buffers that have been discarded by clients and need to be evicted from per-layer caches so
     // the graphics memory can be immediately freed.
@@ -1221,6 +1215,7 @@
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
     bool mIsUserBuild = true;
+    bool mHasReliablePresentFences = false;
 
     // Can only accessed from the main thread, these members
     // don't need synchronization
@@ -1239,21 +1234,22 @@
     // TODO: Also move visibleRegions over to a boolean system.
     bool mUpdateInputInfo = false;
     bool mSomeChildrenChanged;
-    bool mForceTransactionDisplayChange = false;
     bool mUpdateAttachedChoreographer = false;
 
-    // Set if LayerMetadata has changed since the last LayerMetadata snapshot.
-    bool mLayerMetadataSnapshotNeeded = false;
+    struct LayerIntHash {
+        size_t operator()(const std::pair<sp<Layer>, gui::GameMode>& k) const {
+            return std::hash<Layer*>()(k.first.get()) ^
+                    std::hash<int32_t>()(static_cast<int32_t>(k.second));
+        }
+    };
 
     // TODO(b/238781169) validate these on composition
     // Tracks layers that have pending frames which are candidates for being
     // latched.
-    std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
+    std::unordered_set<std::pair<sp<Layer>, gui::GameMode>, LayerIntHash> mLayersWithQueuedFrames;
     std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithBuffersRemoved;
     std::unordered_set<uint32_t> mLayersIdsWithQueuedFrames;
 
-    // Tracks layers that need to update a display's dirty region.
-    std::vector<sp<Layer>> mLayersPendingRefresh;
     // Sorted list of layers that were composed during previous frame. This is used to
     // avoid an expensive traversal of the layer hierarchy when there are no
     // visible region changes. Because this is a list of strong pointers, this will
@@ -1268,7 +1264,6 @@
     };
 
     bool mIsHdcpViaNegVsync = false;
-    bool mIsHotplugErrViaNegVsync = false;
 
     std::mutex mHotplugMutex;
     std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mHotplugMutex);
@@ -1281,10 +1276,10 @@
 
     display::PhysicalDisplays mPhysicalDisplays GUARDED_BY(mStateLock);
 
-    // The inner or outer display for foldables, assuming they have mutually exclusive power states.
-    // Atomic because writes from onActiveDisplayChangedLocked are not always under mStateLock, but
-    // reads from ISchedulerCallback::requestDisplayModes may happen concurrently.
-    std::atomic<PhysicalDisplayId> mActiveDisplayId GUARDED_BY(mStateLock);
+    // The inner or outer display for foldables, while unfolded or folded, respectively.
+    std::atomic<PhysicalDisplayId> mActiveDisplayId;
+
+    display::DisplayModeController mDisplayModeController;
 
     struct {
         DisplayIdGenerator<GpuVirtualDisplayId> gpu;
@@ -1382,38 +1377,11 @@
     // Flag used to set override desired display mode from backdoor
     bool mDebugDisplayModeSetByBackdoor = false;
 
-    // A set of layers that have no parent so they are not drawn on screen.
-    // Should only be accessed by the main thread.
-    // The Layer pointer is removed from the set when the destructor is called so there shouldn't
-    // be any issues with a raw pointer referencing an invalid object.
-    std::unordered_set<Layer*> mOffscreenLayers;
-
     BufferCountTracker mBufferCountTracker;
 
     std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
             GUARDED_BY(mStateLock);
 
-    mutable std::mutex mCreatedLayersLock;
-
-    // A temporay pool that store the created layers and will be added to current state in main
-    // thread.
-    std::vector<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
-    bool commitCreatedLayers(VsyncId, std::vector<LayerCreatedState>& createdLayers);
-    void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock);
-
-    mutable std::mutex mMirrorDisplayLock;
-    struct MirrorDisplayState {
-        MirrorDisplayState(ui::LayerStack layerStack, sp<IBinder>& rootHandle,
-                           const sp<Client>& client)
-              : layerStack(layerStack), rootHandle(rootHandle), client(client) {}
-
-        ui::LayerStack layerStack;
-        sp<IBinder> rootHandle;
-        const sp<Client> client;
-    };
-    std::vector<MirrorDisplayState> mMirrorDisplays GUARDED_BY(mMirrorDisplayLock);
-    bool commitMirrorDisplays(VsyncId);
-
     std::atomic<ui::Transform::RotationFlags> mActiveDisplayTransformHint;
 
     // Must only be accessed on the main thread.
@@ -1447,27 +1415,30 @@
     }
 
     bool mPowerHintSessionEnabled;
+    // Whether a display should be turned on when initialized
+    bool mSkipPowerOnForQuiescent;
 
-    bool mLayerLifecycleManagerEnabled = false;
-    bool mLegacyFrontEndEnabled = true;
+    frontend::LayerLifecycleManager mLayerLifecycleManager GUARDED_BY(kMainThreadContext);
+    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder GUARDED_BY(kMainThreadContext);
+    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder GUARDED_BY(kMainThreadContext);
 
-    frontend::LayerLifecycleManager mLayerLifecycleManager;
-    frontend::LayerHierarchyBuilder mLayerHierarchyBuilder;
-    frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
-
-    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
-    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
-    std::vector<LayerCreationArgs> mNewLayerArgs;
+    mutable std::mutex mCreatedLayersLock;
+    std::vector<sp<Layer>> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
+    std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles GUARDED_BY(mCreatedLayersLock);
+    std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers
+            GUARDED_BY(mCreatedLayersLock);
+    std::vector<LayerCreationArgs> mNewLayerArgs GUARDED_BY(mCreatedLayersLock);
     // These classes do not store any client state but help with managing transaction callbacks
     // and stats.
-    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
+    std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers GUARDED_BY(kMainThreadContext);
 
-    TransactionHandler mTransactionHandler;
-    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
-    bool mFrontEndDisplayInfosChanged = false;
+    TransactionHandler mTransactionHandler GUARDED_BY(kMainThreadContext);
+    ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos
+            GUARDED_BY(kMainThreadContext);
+    bool mFrontEndDisplayInfosChanged GUARDED_BY(kMainThreadContext) = false;
 
     // WindowInfo ids visible during the last commit.
-    std::unordered_set<int32_t> mVisibleWindowIds;
+    std::unordered_set<int32_t> mVisibleWindowIds GUARDED_BY(kMainThreadContext);
 
     // Mirroring
     // Map of displayid to mirrorRoot
@@ -1511,7 +1482,7 @@
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
 public:
-    SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}
+    explicit SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}
 
     binder::Status bootFinished() override;
     binder::Status createDisplayEventConnection(
@@ -1519,9 +1490,10 @@
             const sp<IBinder>& layerHandle,
             sp<gui::IDisplayEventConnection>* outConnection) override;
     binder::Status createConnection(sp<gui::ISurfaceComposerClient>* outClient) override;
-    binder::Status createDisplay(const std::string& displayName, bool secure,
-                                 float requestedRefreshRate, sp<IBinder>* outDisplay) override;
-    binder::Status destroyDisplay(const sp<IBinder>& display) override;
+    binder::Status createVirtualDisplay(const std::string& displayName, bool isSecure,
+                                        const std::string& uniqueId, float requestedRefreshRate,
+                                        sp<IBinder>* outDisplay) override;
+    binder::Status destroyVirtualDisplay(const sp<IBinder>& displayToken) override;
     binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
     binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
     binder::Status setPowerMode(const sp<IBinder>& display, int mode) override;
@@ -1569,7 +1541,6 @@
     binder::Status overrideHdrTypes(const sp<IBinder>& display,
                                     const std::vector<int32_t>& hdrTypes) override;
     binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override;
-    binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) override;
     binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override;
     binder::Status getDisplayedContentSamplingAttributes(
             const sp<IBinder>& display, gui::ContentSamplingAttributes* outAttrs) override;
@@ -1633,6 +1604,12 @@
     binder::Status getStalledTransactionInfo(
             int pid, std::optional<gui::StalledTransactionInfo>* outInfo) override;
     binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
+    binder::Status notifyShutdown() override;
+    binder::Status addJankListener(const sp<IBinder>& layer,
+                                   const sp<gui::IJankListener>& listener) override;
+    binder::Status flushJankData(int32_t layerId) override;
+    binder::Status removeJankListener(int32_t layerId, const sp<gui::IJankListener>& listener,
+                                      int64_t afterVsync) override;
 
 private:
     static const constexpr bool kUsePermissionCache = true;
@@ -1643,7 +1620,7 @@
                                               gui::DynamicDisplayInfo*& outInfo);
 
 private:
-    sp<SurfaceFlinger> mFlinger;
+    const sp<SurfaceFlinger> mFlinger;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 7e6894d..b1d8ba9 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -26,7 +26,6 @@
 #include "FrameTracer/FrameTracer.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlingerDefaultFactory.h"
 #include "SurfaceFlingerProperties.h"
 
@@ -53,11 +52,6 @@
     }
 }
 
-sp<StartPropertySetThread> DefaultFactory::createStartPropertySetThread(
-        bool timestampPropertyValue) {
-    return sp<StartPropertySetThread>::make(timestampPropertyValue);
-}
-
 sp<DisplayDevice> DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) {
     return sp<DisplayDevice>::make(creationArgs);
 }
@@ -91,7 +85,7 @@
     return sp<Layer>::make(args);
 }
 
-sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName) {
+sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName, const Layer* /* owner */) {
     return sp<LayerFE>::make(layerName);
 }
 
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 2c6de0e..7ebf10f 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,6 @@
     std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) override;
-    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) override;
     sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
                                           uint32_t layerCount, uint64_t usage,
@@ -42,7 +41,7 @@
     std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override;
     sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) override;
     sp<Layer> createEffectLayer(const LayerCreationArgs& args) override;
-    sp<LayerFE> createLayerFE(const std::string& layerName) override;
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) override;
     std::unique_ptr<FrameTracer> createFrameTracer() override;
     std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index f310c4a..c7d1fa0 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerFactory.h
@@ -39,7 +39,6 @@
 class IGraphicBufferProducer;
 class Layer;
 class LayerFE;
-class StartPropertySetThread;
 class SurfaceFlinger;
 class TimeStats;
 
@@ -71,8 +70,6 @@
     virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps currentRefreshRate) = 0;
 
-    virtual sp<StartPropertySetThread> createStartPropertySetThread(
-            bool timestampPropertyValue) = 0;
     virtual sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) = 0;
     virtual sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height,
                                                   PixelFormat format, uint32_t layerCount,
@@ -88,7 +85,7 @@
 
     virtual sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
     virtual sp<Layer> createEffectLayer(const LayerCreationArgs& args) = 0;
-    virtual sp<LayerFE> createLayerFE(const std::string& layerName) = 0;
+    virtual sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* owner) = 0;
     virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
     virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
             std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index a631074..a6a0152 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -20,10 +20,12 @@
         "libtimestats_atoms_proto",
         "libui",
         "libutils",
+        "libtracing_perfetto",
     ],
 
     static_libs: [
         "libtimestats_proto",
+        "libsurfaceflinger_common",
     ],
 
     export_static_lib_headers: [
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index 368cb41..c60ded6 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -19,11 +19,11 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include <android-base/stringprintf.h>
+#include <common/trace.h>
 #include <log/log.h>
 #include <timestatsatomsproto/TimeStatsAtomsProtoHeader.h>
 #include <utils/String8.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 #include <algorithm>
 #include <chrono>
@@ -271,7 +271,7 @@
 }
 
 void TimeStats::parseArgs(bool asProto, const Vector<String16>& args, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::unordered_map<std::string, int32_t> argsMap;
     for (size_t index = 0; index < args.size(); ++index) {
@@ -304,7 +304,7 @@
 }
 
 std::string TimeStats::miniDump() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::string result = "TimeStats miniDump:\n";
     std::lock_guard<std::mutex> lock(mMutex);
@@ -318,7 +318,7 @@
 void TimeStats::incrementTotalFrames() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.totalFramesLegacy++;
@@ -327,7 +327,7 @@
 void TimeStats::incrementMissedFrames() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.missedFramesLegacy++;
@@ -338,7 +338,7 @@
         return;
     }
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (record.changed) mTimeStats.compositionStrategyChangesLegacy++;
@@ -351,7 +351,7 @@
 void TimeStats::incrementRefreshRateSwitches() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStats.refreshRateSwitchesLegacy++;
@@ -445,7 +445,7 @@
                                                    std::optional<Fps> renderRate,
                                                    SetFrameRateVote frameRateVote,
                                                    GameMode gameMode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-flushAvailableRecordsToStatsLocked", layerId);
 
     LayerRecord& layerRecord = mTimeStatsTracker[layerId];
@@ -568,7 +568,7 @@
                             uid_t uid, nsecs_t postTime, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-[%s]-PostTime[%" PRId64 "]", layerId, frameNumber, layerName.c_str(),
           postTime);
 
@@ -612,7 +612,7 @@
 void TimeStats::setLatchTime(int32_t layerId, uint64_t frameNumber, nsecs_t latchTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-LatchTime[%" PRId64 "]", layerId, frameNumber, latchTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -630,7 +630,7 @@
 void TimeStats::incrementLatchSkipped(int32_t layerId, LatchSkipReason reason) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-LatchSkipped-Reason[%d]", layerId,
           static_cast<std::underlying_type<LatchSkipReason>::type>(reason));
 
@@ -648,7 +648,7 @@
 void TimeStats::incrementBadDesiredPresent(int32_t layerId) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-BadDesiredPresent", layerId);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -660,7 +660,7 @@
 void TimeStats::setDesiredTime(int32_t layerId, uint64_t frameNumber, nsecs_t desiredTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-DesiredTime[%" PRId64 "]", layerId, frameNumber, desiredTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -678,7 +678,7 @@
 void TimeStats::setAcquireTime(int32_t layerId, uint64_t frameNumber, nsecs_t acquireTime) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-AcquireTime[%" PRId64 "]", layerId, frameNumber, acquireTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -697,7 +697,7 @@
                                 const std::shared_ptr<FenceTime>& acquireFence) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-AcquireFenceTime[%" PRId64 "]", layerId, frameNumber,
           acquireFence->getSignalTime());
 
@@ -718,7 +718,7 @@
                                SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-PresentTime[%" PRId64 "]", layerId, frameNumber, presentTime);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -744,7 +744,7 @@
                                 SetFrameRateVote frameRateVote, GameMode gameMode) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-PresentFenceTime[%" PRId64 "]", layerId, frameNumber,
           presentFence->getSignalTime());
 
@@ -805,7 +805,7 @@
 void TimeStats::incrementJankyFrames(const JankyFramesInfo& info) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mMutex);
 
     // Only update layer stats if we're already tracking the layer in TimeStats.
@@ -861,7 +861,7 @@
 }
 
 void TimeStats::onDestroy(int32_t layerId) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-onDestroy", layerId);
     std::lock_guard<std::mutex> lock(mMutex);
     mTimeStatsTracker.erase(layerId);
@@ -870,7 +870,7 @@
 void TimeStats::removeTimeRecord(int32_t layerId, uint64_t frameNumber) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     ALOGV("[%d]-[%" PRIu64 "]-removeTimeRecord", layerId, frameNumber);
 
     std::lock_guard<std::mutex> lock(mMutex);
@@ -935,7 +935,7 @@
 }
 
 void TimeStats::flushAvailableGlobalRecordsToStatsLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     while (!mGlobalRecord.presentFences.empty()) {
         const nsecs_t curPresentTime = mGlobalRecord.presentFences.front()->getSignalTime();
@@ -992,7 +992,7 @@
 void TimeStats::setPresentFenceGlobal(const std::shared_ptr<FenceTime>& presentFence) {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
     std::lock_guard<std::mutex> lock(mMutex);
     if (presentFence == nullptr || !presentFence->isValid()) {
         mGlobalRecord.prevPresentTime = 0;
@@ -1022,7 +1022,7 @@
 void TimeStats::enable() {
     if (mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     mEnabled.store(true);
@@ -1034,7 +1034,7 @@
 void TimeStats::disable() {
     if (!mEnabled.load()) return;
 
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     flushPowerTimeLocked();
@@ -1051,7 +1051,7 @@
 }
 
 void TimeStats::clearGlobalLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     mTimeStats.statsStartLegacy = (mEnabled.load() ? static_cast<int64_t>(std::time(0)) : 0);
     mTimeStats.statsEndLegacy = 0;
@@ -1078,7 +1078,7 @@
 }
 
 void TimeStats::clearLayersLocked() {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     mTimeStatsTracker.clear();
 
@@ -1093,7 +1093,7 @@
 }
 
 void TimeStats::dump(bool asProto, std::optional<uint32_t> maxLayers, std::string& result) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
 
     std::lock_guard<std::mutex> lock(mMutex);
     if (mTimeStats.statsStartLegacy == 0) {
diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h
index 1adc3a5..51f33a6 100644
--- a/services/surfaceflinger/TracedOrdinal.h
+++ b/services/surfaceflinger/TracedOrdinal.h
@@ -21,8 +21,8 @@
 #include <functional>
 #include <string>
 
+#include <common/trace.h>
 #include <cutils/compiler.h>
-#include <utils/Trace.h>
 
 namespace android {
 
@@ -79,7 +79,7 @@
 
 private:
     void trace() {
-        if (CC_LIKELY(!ATRACE_ENABLED())) {
+        if (CC_LIKELY(!SFTRACE_ENABLED())) {
             return;
         }
 
@@ -88,13 +88,13 @@
         }
 
         if (!signbit(mData)) {
-            ATRACE_INT64(mName.c_str(), to_int64(mData));
+            SFTRACE_INT64(mName.c_str(), to_int64(mData));
             if (mHasGoneNegative) {
-                ATRACE_INT64(mNameNegative.c_str(), 0);
+                SFTRACE_INT64(mNameNegative.c_str(), 0);
             }
         } else {
-            ATRACE_INT64(mNameNegative.c_str(), -to_int64(mData));
-            ATRACE_INT64(mName.c_str(), 0);
+            SFTRACE_INT64(mNameNegative.c_str(), -to_int64(mData));
+            SFTRACE_INT64(mName.c_str(), 0);
         }
     }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index 41bcdf0..d40b888 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -24,10 +24,10 @@
 #include "Tracing/tools/LayerTraceGenerator.h"
 #include "TransactionTracing.h"
 
+#include <common/trace.h>
 #include <log/log.h>
 #include <perfetto/tracing.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 namespace android {
 
@@ -134,7 +134,7 @@
 
 void LayerTracing::addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot,
                                              Mode mode) {
-    ATRACE_CALL();
+    SFTRACE_CALL();
     if (mOutStream) {
         writeSnapshotToStream(std::move(snapshot));
     } else {
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index 2dc89b5..b189598 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -62,12 +62,12 @@
 
     proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(t.states.size()));
     for (auto& layerState : t.states) {
-        proto.mutable_layer_changes()->Add(std::move(toProto(layerState)));
+        proto.mutable_layer_changes()->Add(toProto(layerState));
     }
 
     proto.mutable_display_changes()->Reserve(static_cast<int32_t>(t.displays.size()));
     for (auto& displayState : t.displays) {
-        proto.mutable_display_changes()->Add(std::move(toProto(displayState)));
+        proto.mutable_display_changes()->Add(toProto(displayState));
     }
 
     proto.mutable_merged_transaction_ids()->Reserve(
@@ -247,7 +247,8 @@
         proto.set_auto_refresh(layer.autoRefresh);
     }
     if (layer.what & layer_state_t::eTrustedOverlayChanged) {
-        proto.set_is_trusted_overlay(layer.isTrustedOverlay);
+        proto.set_is_trusted_overlay(layer.trustedOverlay == gui::TrustedOverlay::ENABLED);
+        // TODO(b/339701674) update protos
     }
     if (layer.what & layer_state_t::eBufferCropChanged) {
         LayerProtoHelper::writeToProto(layer.bufferCrop, proto.mutable_buffer_crop());
@@ -435,6 +436,7 @@
         layer.bufferData->flags = ftl::Flags<BufferData::BufferDataChange>(bufferProto.flags());
         layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
         layer.bufferData->acquireFence = Fence::NO_FENCE;
+        layer.bufferData->dequeueTime = -1;
     }
 
     if (proto.what() & layer_state_t::eApiChanged) {
@@ -515,7 +517,8 @@
         layer.autoRefresh = proto.auto_refresh();
     }
     if (proto.what() & layer_state_t::eTrustedOverlayChanged) {
-        layer.isTrustedOverlay = proto.is_trusted_overlay();
+        layer.trustedOverlay = proto.is_trusted_overlay() ? gui::TrustedOverlay::ENABLED
+                                                          : gui::TrustedOverlay::UNSET;
     }
     if (proto.what() & layer_state_t::eBufferCropChanged) {
         LayerProtoHelper::readFromProto(proto.buffer_crop(), layer.bufferCrop);
@@ -566,7 +569,7 @@
         const frontend::DisplayInfo& displayInfo, uint32_t layerStack) {
     perfetto::protos::DisplayInfo proto;
     proto.set_layer_stack(layerStack);
-    proto.set_display_id(displayInfo.info.displayId);
+    proto.set_display_id(displayInfo.info.displayId.val());
     proto.set_logical_width(displayInfo.info.logicalWidth);
     proto.set_logical_height(displayInfo.info.logicalHeight);
     asProto(proto.mutable_transform_inverse(), displayInfo.info.transform);
@@ -588,7 +591,7 @@
 frontend::DisplayInfo TransactionProtoParser::fromProto(
         const perfetto::protos::DisplayInfo& proto) {
     frontend::DisplayInfo displayInfo;
-    displayInfo.info.displayId = proto.display_id();
+    displayInfo.info.displayId = ui::LogicalDisplayId{proto.display_id()};
     displayInfo.info.logicalWidth = proto.logical_width();
     displayInfo.info.logicalHeight = proto.logical_height();
     fromProto2(displayInfo.info.transform, proto.transform_inverse());
diff --git a/services/surfaceflinger/Tracing/TransactionRingBuffer.h b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
index 7d1d3fd..2b66391 100644
--- a/services/surfaceflinger/Tracing/TransactionRingBuffer.h
+++ b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
@@ -19,13 +19,12 @@
 #include <android-base/file.h>
 #include <android-base/stringprintf.h>
 
+#include <common/trace.h>
 #include <log/log.h>
 #include <utils/Errors.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 #include <chrono>
 #include <fstream>
-#include <queue>
 
 namespace android {
 
@@ -57,7 +56,7 @@
     }
 
     status_t appendToStream(FileProto& fileProto, std::ofstream& out) {
-        ATRACE_CALL();
+        SFTRACE_CALL();
         writeToProto(fileProto);
         std::string output;
         if (!fileProto.SerializeToString(&output)) {
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 696f348..bc9f809 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -244,7 +244,7 @@
                 static_cast<int32_t>(update.createdLayers.size()));
 
         for (const auto& args : update.createdLayers) {
-            entryProto.mutable_added_layers()->Add(std::move(mProtoParser.toProto(args)));
+            entryProto.mutable_added_layers()->Add(mProtoParser.toProto(args));
         }
 
         entryProto.mutable_destroyed_layers()->Reserve(
@@ -276,7 +276,7 @@
                     static_cast<int32_t>(update.displayInfos.size()));
             for (auto& [layerStack, displayInfo] : update.displayInfos) {
                 entryProto.mutable_displays()->Add(
-                        std::move(mProtoParser.toProto(displayInfo, layerStack.id)));
+                        mProtoParser.toProto(displayInfo, layerStack.id));
             }
         }
 
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index 8afca41..63c1b37 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -28,6 +28,7 @@
         "libsurfaceflinger_mocks_defaults",
         "librenderengine_deps",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_common_deps",
     ],
     srcs: [
         ":libsurfaceflinger_sources",
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index 617ea2c..1dba175 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -162,7 +162,10 @@
 
         auto layersProto =
                 LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, traceFlags)
-                        .generate(hierarchyBuilder.getHierarchy());
+                        .with(hierarchyBuilder.getHierarchy())
+                        .withOffscreenLayers(hierarchyBuilder.getOffscreenHierarchy())
+                        .generate();
+
         auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
         if (!onlyLastEntry || (i == traceFile.entry_size() - 1)) {
             perfetto::protos::LayersSnapshotProto snapshotProto{};
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.cpp b/services/surfaceflinger/TransactionCallbackInvoker.cpp
index 7b5298c..c6856ae 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.cpp
+++ b/services/surfaceflinger/TransactionCallbackInvoker.cpp
@@ -27,9 +27,9 @@
 #include "BackgroundExecutor.h"
 #include "Utils/FenceUtils.h"
 
-#include <cinttypes>
-
 #include <binder/IInterface.h>
+#include <common/FlagManager.h>
+#include <common/trace.h>
 #include <utils/RefBase.h>
 
 namespace android {
@@ -63,13 +63,12 @@
     if (handles.empty()) {
         return NO_ERROR;
     }
-    const std::vector<JankData>& jankData = std::vector<JankData>();
     for (const auto& handle : handles) {
         if (!containsOnCommitCallbacks(handle->callbackIds)) {
             outRemainingHandles.push_back(handle);
             continue;
         }
-        status_t err = addCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle);
         if (err != NO_ERROR) {
             return err;
         }
@@ -79,12 +78,12 @@
 }
 
 status_t TransactionCallbackInvoker::addCallbackHandles(
-        const std::deque<sp<CallbackHandle>>& handles, const std::vector<JankData>& jankData) {
+        const std::deque<sp<CallbackHandle>>& handles) {
     if (handles.empty()) {
         return NO_ERROR;
     }
     for (const auto& handle : handles) {
-        status_t err = addCallbackHandle(handle, jankData);
+        status_t err = addCallbackHandle(handle);
         if (err != NO_ERROR) {
             return err;
         }
@@ -110,8 +109,7 @@
     return NO_ERROR;
 }
 
-status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& handle,
-        const std::vector<JankData>& jankData) {
+status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>& handle) {
     // If we can't find the transaction stats something has gone wrong. The client should call
     // startRegistration before trying to add a callback handle.
     TransactionStats* transactionStats;
@@ -128,9 +126,17 @@
     sp<IBinder> surfaceControl = handle->surfaceControl.promote();
     if (surfaceControl) {
         sp<Fence> prevFence = nullptr;
-        for (const auto& future : handle->previousReleaseFences) {
-            mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+
+        if (FlagManager::getInstance().ce_fence_promise()) {
+            for (auto& future : handle->previousReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+            }
+        } else {
+            for (const auto& future : handle->previousSharedReleaseFences) {
+                mergeFence(handle->name.c_str(), future.get().value_or(Fence::NO_FENCE), prevFence);
+            }
         }
+
         handle->previousReleaseFence = prevFence;
         handle->previousReleaseFences.clear();
 
@@ -142,8 +148,14 @@
                                                     handle->previousReleaseFence,
                                                     handle->transformHint,
                                                     handle->currentMaxAcquiredBufferCount,
-                                                    eventStats, jankData,
-                                                    handle->previousReleaseCallbackId);
+                                                    eventStats, handle->previousReleaseCallbackId);
+        if (handle->bufferReleaseChannel &&
+            handle->previousReleaseCallbackId != ReleaseCallbackId::INVALID_ID) {
+            mBufferReleases.emplace_back(handle->bufferReleaseChannel,
+                                         handle->previousReleaseCallbackId,
+                                         handle->previousReleaseFence,
+                                         handle->currentMaxAcquiredBufferCount);
+        }
     }
     return NO_ERROR;
 }
@@ -153,9 +165,15 @@
 }
 
 void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) {
+    for (const auto& bufferRelease : mBufferReleases) {
+        bufferRelease.channel->writeReleaseFence(bufferRelease.callbackId, bufferRelease.fence,
+                                                 bufferRelease.currentMaxAcquiredBufferCount);
+    }
+    mBufferReleases.clear();
+
     // For each listener
     auto completedTransactionsItr = mCompletedTransactions.begin();
-    BackgroundExecutor::Callbacks callbacks;
+    ftl::SmallVector<ListenerStats, 10> listenerStatsToSend;
     while (completedTransactionsItr != mCompletedTransactions.end()) {
         auto& [listener, transactionStatsDeque] = *completedTransactionsItr;
         ListenerStats listenerStats;
@@ -190,10 +208,7 @@
                 // keep it as an IBinder due to consistency reasons: if we
                 // interface_cast at the IPC boundary when reading a Parcel,
                 // we get pointers that compare unequal in the SF process.
-                callbacks.emplace_back([stats = std::move(listenerStats)]() {
-                    interface_cast<ITransactionCompletedListener>(stats.listener)
-                            ->onTransactionCompleted(stats);
-                });
+                listenerStatsToSend.emplace_back(std::move(listenerStats));
             }
         }
         completedTransactionsItr++;
@@ -203,7 +218,14 @@
         mPresentFence.clear();
     }
 
-    BackgroundExecutor::getInstance().sendCallbacks(std::move(callbacks));
+    BackgroundExecutor::getInstance().sendCallbacks(
+            {[listenerStatsToSend = std::move(listenerStatsToSend)]() {
+                SFTRACE_NAME("TransactionCallbackInvoker::sendCallbacks");
+                for (auto& stats : listenerStatsToSend) {
+                    interface_cast<ITransactionCompletedListener>(stats.listener)
+                            ->onTransactionCompleted(stats);
+                }
+            }});
 }
 
 // -----------------------------------------------------------------------
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 245398f..14a7487 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -16,18 +16,14 @@
 
 #pragma once
 
-#include <condition_variable>
 #include <deque>
-#include <mutex>
 #include <optional>
-#include <queue>
-#include <thread>
 #include <unordered_map>
-#include <unordered_set>
 
 #include <android-base/thread_annotations.h>
 #include <binder/IBinder.h>
 #include <ftl/future.h>
+#include <gui/BufferReleaseChannel.h>
 #include <gui/ITransactionCompletedListener.h>
 #include <ui/Fence.h>
 #include <ui/FenceResult.h>
@@ -46,7 +42,8 @@
     bool releasePreviousBuffer = false;
     std::string name;
     sp<Fence> previousReleaseFence;
-    std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
+    std::vector<ftl::Future<FenceResult>> previousReleaseFences;
+    std::vector<ftl::SharedFuture<FenceResult>> previousSharedReleaseFences;
     std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
     nsecs_t latchTime = -1;
     std::optional<uint32_t> transformHint = std::nullopt;
@@ -58,12 +55,12 @@
     uint64_t frameNumber = 0;
     uint64_t previousFrameNumber = 0;
     ReleaseCallbackId previousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
+    std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> bufferReleaseChannel;
 };
 
 class TransactionCallbackInvoker {
 public:
-    status_t addCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
-                                const std::vector<JankData>& jankData);
+    status_t addCallbackHandles(const std::deque<sp<CallbackHandle>>& handles);
     status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
                                              std::deque<sp<CallbackHandle>>& outRemainingHandles);
 
@@ -76,9 +73,7 @@
         mCompletedTransactions.clear();
     }
 
-    status_t addCallbackHandle(const sp<CallbackHandle>& handle,
-                               const std::vector<JankData>& jankData);
-
+    status_t addCallbackHandle(const sp<CallbackHandle>& handle);
 
 private:
     status_t findOrCreateTransactionStats(const sp<IBinder>& listener,
@@ -88,6 +83,14 @@
     std::unordered_map<sp<IBinder>, std::deque<TransactionStats>, IListenerHash>
         mCompletedTransactions;
 
+    struct BufferRelease {
+        std::shared_ptr<gui::BufferReleaseChannel::ProducerEndpoint> channel;
+        ReleaseCallbackId callbackId;
+        sp<Fence> fence;
+        uint32_t currentMaxAcquiredBufferCount;
+    };
+    std::vector<BufferRelease> mBufferReleases;
+
     sp<Fence> mPresentFence;
 };
 
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 31cd2d7..e5d6481 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -23,6 +23,7 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "renderengine/ExternalTexture.h"
 
+#include <common/FlagManager.h>
 #include <gui/LayerState.h>
 #include <system/window.h>
 
@@ -86,7 +87,7 @@
     }
 
     template <typename Visitor>
-    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) {
+    void traverseStatesWithBuffersWhileTrue(Visitor&& visitor) NO_THREAD_SAFETY_ANALYSIS {
         for (auto state = states.begin(); state != states.end();) {
             if (state->state.hasBufferChanges() && state->externalTexture && state->state.surface) {
                 int result = visitor(*state);
@@ -108,9 +109,22 @@
 
         for (const auto& state : states) {
             const bool frameRateChanged = state.state.what & layer_state_t::eFrameRateChanged;
-            if (!frameRateChanged ||
-                state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
-                return true;
+            if (FlagManager::getInstance().vrr_bugfix_24q4()) {
+                const bool frameRateIsNoVote = frameRateChanged &&
+                        state.state.frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_NO_VOTE;
+                const bool frameRateCategoryChanged =
+                        state.state.what & layer_state_t::eFrameRateCategoryChanged;
+                const bool frameRateCategoryIsNoPreference = frameRateCategoryChanged &&
+                        state.state.frameRateCategory ==
+                                ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                if (!frameRateIsNoVote && !frameRateCategoryIsNoPreference) {
+                    return true;
+                }
+            } else {
+                if (!frameRateChanged ||
+                    state.state.frameRateCompatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE) {
+                    return true;
+                }
             }
         }
 
diff --git a/services/surfaceflinger/Utils/OnceFuture.h b/services/surfaceflinger/Utils/OnceFuture.h
new file mode 100644
index 0000000..412038c
--- /dev/null
+++ b/services/surfaceflinger/Utils/OnceFuture.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <future>
+#include <mutex>
+
+#include <android-base/thread_annotations.h>
+
+namespace android::utils {
+
+// Allows a thread to `wait` for a future produced by a different thread. The future is returned by
+// the first call to a function `F` that multiple threads may `callOnce`. If no `callOnce` happens,
+// then `wait` does nothing. Otherwise, it blocks on the future, then destroys it, which resets the
+// `OnceFuture`.
+class OnceFuture {
+public:
+    template <typename F>
+    void callOnce(F f) {
+        std::lock_guard lock(mMutex);
+        if (!mFuture.valid()) {
+            mFuture = f();
+        }
+    }
+
+    void wait() {
+        std::lock_guard lock(mMutex);
+        if (mFuture.valid()) {
+            mFuture.wait();
+            mFuture = {};
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::future<void> mFuture GUARDED_BY(mMutex);
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/Utils/RingBuffer.h b/services/surfaceflinger/Utils/RingBuffer.h
index 198e7b2..215472b 100644
--- a/services/surfaceflinger/Utils/RingBuffer.h
+++ b/services/surfaceflinger/Utils/RingBuffer.h
@@ -43,8 +43,10 @@
     }
 
     T& front() { return (*this)[0]; }
+    const T& front() const { return (*this)[0]; }
 
     T& back() { return (*this)[size() - 1]; }
+    const T& back() const { return (*this)[size() - 1]; }
 
     T& operator[](size_t index) {
         return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index effbfdb..895e054 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -17,8 +17,8 @@
 #include <android/gui/BnWindowInfosPublisher.h>
 #include <android/gui/IWindowInfosPublisher.h>
 #include <android/gui/WindowInfosListenerInfo.h>
+#include <common/trace.h>
 #include <gui/ISurfaceComposer.h>
-#include <gui/TraceUtils.h>
 #include <gui/WindowInfosUpdate.h>
 #include <scheduler/Time.h>
 
@@ -42,7 +42,7 @@
 
     BackgroundExecutor::getInstance().sendCallbacks(
             {[this, listener = std::move(listener), listenerId]() {
-                ATRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener");
+                SFTRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener");
                 sp<IBinder> asBinder = IInterface::asBinder(listener);
                 asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
                 mWindowInfosListeners.try_emplace(asBinder,
@@ -53,7 +53,7 @@
 void WindowInfosListenerInvoker::removeWindowInfosListener(
         const sp<IWindowInfosListener>& listener) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, listener]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
+        SFTRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
         sp<IBinder> asBinder = IInterface::asBinder(listener);
         asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
         eraseListenerAndAckMessages(asBinder);
@@ -62,7 +62,7 @@
 
 void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
+        SFTRACE_NAME("WindowInfosListenerInvoker::binderDied");
         eraseListenerAndAckMessages(who);
     }});
 }
@@ -146,7 +146,7 @@
 WindowInfosListenerInvoker::DebugInfo WindowInfosListenerInvoker::getDebugInfo() {
     DebugInfo result;
     BackgroundExecutor::getInstance().sendCallbacks({[&, this]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::getDebugInfo");
+        SFTRACE_NAME("WindowInfosListenerInvoker::getDebugInfo");
         updateMaxSendDelay();
         result = mDebugInfo;
         result.pendingMessageCount = mUnackedState.size();
@@ -169,7 +169,7 @@
 binder::Status WindowInfosListenerInvoker::ackWindowInfosReceived(int64_t vsyncId,
                                                                   int64_t listenerId) {
     BackgroundExecutor::getInstance().sendCallbacks({[this, vsyncId, listenerId]() {
-        ATRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived");
+        SFTRACE_NAME("WindowInfosListenerInvoker::ackWindowInfosReceived");
         auto it = mUnackedState.find(vsyncId);
         if (it == mUnackedState.end()) {
             return;
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
index f2ff00b..f9c99bf 100644
--- a/services/surfaceflinger/common/Android.bp
+++ b/services/surfaceflinger/common/Android.bp
@@ -17,6 +17,8 @@
     shared_libs: [
         "libSurfaceFlingerProp",
         "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
     ],
     static_libs: [
         "librenderengine_includes",
@@ -26,6 +28,7 @@
     ],
     local_include_dirs: ["include"],
     export_include_dirs: ["include"],
+    export_shared_lib_headers: ["libtracing_perfetto"],
 }
 
 cc_library_static {
@@ -35,6 +38,9 @@
     ],
     static_libs: [
         "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
 
@@ -45,5 +51,40 @@
     ],
     static_libs: [
         "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_deps",
+    shared_libs: [
+        "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+        "android.os.flags-aconfig-cc",
+        "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
+    ],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_test_deps",
+    shared_libs: [
+        "server_configurable_flags",
+        "libaconfig_storage_read_api_cc",
+        "libtracing_perfetto",
+    ],
+    static_libs: [
+        "libsurfaceflinger_common_test",
+        "libsurfaceflingerflags_test",
+        "android.os.flags-aconfig-cc-test",
+        "android.server.display.flags-aconfig-cc",
+        "libguiflags_no_apex",
     ],
 }
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index b7f06a9..12d6138 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -26,7 +26,10 @@
 #include <server_configurable_flags/get_flags.h>
 #include <cinttypes>
 
+#include <android_os.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <com_android_graphics_surfaceflinger_flags.h>
+#include <com_android_server_display_feature_flags.h>
 
 namespace android {
 using namespace com::android::graphics::surfaceflinger;
@@ -109,10 +112,13 @@
 
     /// Trunk stable server flags ///
     DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+    DUMP_SERVER_FLAG(adpf_gpu_sf);
+    DUMP_SERVER_FLAG(adpf_use_fmq_channel);
 
     /// Trunk stable readonly flags ///
     DUMP_READ_ONLY_FLAG(connected_display);
     DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+    DUMP_READ_ONLY_FLAG(frame_rate_category_mrr);
     DUMP_READ_ONLY_FLAG(misc1);
     DUMP_READ_ONLY_FLAG(vrr_config);
     DUMP_READ_ONLY_FLAG(hotplug2);
@@ -129,9 +135,29 @@
     DUMP_READ_ONLY_FLAG(screenshot_fence_preservation);
     DUMP_READ_ONLY_FLAG(vulkan_renderengine);
     DUMP_READ_ONLY_FLAG(renderable_buffer_usage);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_24q4);
+    DUMP_READ_ONLY_FLAG(vrr_bugfix_dropped_frame);
     DUMP_READ_ONLY_FLAG(restore_blur_step);
     DUMP_READ_ONLY_FLAG(dont_skip_on_early_ro);
     DUMP_READ_ONLY_FLAG(protected_if_client);
+    DUMP_READ_ONLY_FLAG(ce_fence_promise);
+    DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
+    DUMP_READ_ONLY_FLAG(graphite_renderengine);
+    DUMP_READ_ONLY_FLAG(filter_frames_before_trace_starts);
+    DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
+    DUMP_READ_ONLY_FLAG(deprecate_vsync_sf);
+    DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter);
+    DUMP_READ_ONLY_FLAG(detached_mirror);
+    DUMP_READ_ONLY_FLAG(commit_not_composited);
+    DUMP_READ_ONLY_FLAG(correct_dpi_with_display_size);
+    DUMP_READ_ONLY_FLAG(local_tonemap_screenshots);
+    DUMP_READ_ONLY_FLAG(override_trusted_overlay);
+    DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache);
+    DUMP_READ_ONLY_FLAG(force_compile_graphite_renderengine);
+    DUMP_READ_ONLY_FLAG(single_hop_screenshot);
+    DUMP_READ_ONLY_FLAG(trace_frame_rate_override);
+    DUMP_READ_ONLY_FLAG(true_hdr_screenshots);
+
 #undef DUMP_READ_ONLY_FLAG
 #undef DUMP_SERVER_FLAG
 #undef DUMP_FLAG_INTERVAL
@@ -158,7 +184,7 @@
         return getServerConfigurableFlag(serverFlagName);                                   \
     }
 
-#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted)                \
+#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted, owner)         \
     bool FlagManager::name() const {                                                            \
         if (checkForBootCompleted) {                                                            \
             LOG_ALWAYS_FATAL_IF(!mBootCompleted,                                                \
@@ -166,21 +192,27 @@
                                 __func__);                                                      \
         }                                                                                       \
         static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride);      \
-        static const bool value = getFlagValue([] { return flags::name(); }, debugOverride);    \
+        static const bool value = getFlagValue([] { return owner ::name(); }, debugOverride);   \
         if (mUnitTestMode) {                                                                    \
             /*                                                                                  \
              * When testing, we don't want to rely on the cached `value` or the debugOverride.  \
              */                                                                                 \
-            return flags::name();                                                               \
+            return owner ::name();                                                              \
         }                                                                                       \
         return value;                                                                           \
     }
 
 #define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, flags)
 
 #define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
-    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false)
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, flags)
+
+#define FLAG_MANAGER_SERVER_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true, owner)
+
+#define FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(name, syspropOverride, owner) \
+    FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false, owner)
 
 /// Legacy server flags ///
 FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
@@ -192,6 +224,7 @@
 /// Trunk stable readonly flags ///
 FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
 FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(frame_rate_category_mrr, "debug.sf.frame_rate_category_mrr")
 FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
 FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
@@ -205,15 +238,43 @@
 FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
 FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
 FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
-FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "debug.sf.enable_layer_command_batching")
 FLAG_MANAGER_READ_ONLY_FLAG(screenshot_fence_preservation, "debug.sf.screenshot_fence_preservation")
 FLAG_MANAGER_READ_ONLY_FLAG(vulkan_renderengine, "debug.renderengine.vulkan")
 FLAG_MANAGER_READ_ONLY_FLAG(renderable_buffer_usage, "")
 FLAG_MANAGER_READ_ONLY_FLAG(restore_blur_step, "debug.renderengine.restore_blur_step")
 FLAG_MANAGER_READ_ONLY_FLAG(dont_skip_on_early_ro, "")
 FLAG_MANAGER_READ_ONLY_FLAG(protected_if_client, "")
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_24q4, "");
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_dropped_frame, "")
+FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
+FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
+FLAG_MANAGER_READ_ONLY_FLAG(filter_frames_before_trace_starts, "")
+FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
+FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, "");
+FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, "");
+FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, "");
+FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, "");
+FLAG_MANAGER_READ_ONLY_FLAG(correct_dpi_with_display_size, "");
+FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
+FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
+FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, "");
+FLAG_MANAGER_READ_ONLY_FLAG(force_compile_graphite_renderengine, "");
+FLAG_MANAGER_READ_ONLY_FLAG(single_hop_screenshot, "");
+FLAG_MANAGER_READ_ONLY_FLAG(true_hdr_screenshots, "debug.sf.true_hdr_screenshots");
 
 /// Trunk stable server flags ///
 FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+FLAG_MANAGER_SERVER_FLAG(adpf_gpu_sf, "")
+
+/// Trunk stable server flags from outside SurfaceFlinger ///
+FLAG_MANAGER_SERVER_FLAG_IMPORTED(adpf_use_fmq_channel, "", android::os)
+
+/// Trunk stable readonly flags from outside SurfaceFlinger ///
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(idle_screen_refresh_rate_timeout, "",
+                                     com::android::server::display::feature::flags)
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(adpf_use_fmq_channel_fixed, "", android::os)
+FLAG_MANAGER_READ_ONLY_FLAG_IMPORTED(trace_frame_rate_override, "",
+                                     com::android::graphics::libgui::flags);
 
 } // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 241c814..a1be194 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -49,9 +49,13 @@
 
     /// Trunk stable server flags ///
     bool refresh_rate_overlay_on_external_display() const;
+    bool adpf_gpu_sf() const;
+    bool adpf_use_fmq_channel() const;
+    bool adpf_use_fmq_channel_fixed() const;
 
     /// Trunk stable readonly flags ///
     bool connected_display() const;
+    bool frame_rate_category_mrr() const;
     bool enable_small_area_detection() const;
     bool misc1() const;
     bool vrr_config() const;
@@ -68,10 +72,29 @@
     bool enable_layer_command_batching() const;
     bool screenshot_fence_preservation() const;
     bool vulkan_renderengine() const;
+    bool vrr_bugfix_24q4() const;
+    bool vrr_bugfix_dropped_frame() const;
     bool renderable_buffer_usage() const;
     bool restore_blur_step() const;
     bool dont_skip_on_early_ro() const;
     bool protected_if_client() const;
+    bool ce_fence_promise() const;
+    bool idle_screen_refresh_rate_timeout() const;
+    bool graphite_renderengine() const;
+    bool filter_frames_before_trace_starts() const;
+    bool latch_unsignaled_with_auto_refresh_changed() const;
+    bool deprecate_vsync_sf() const;
+    bool allow_n_vsyncs_in_targeter() const;
+    bool detached_mirror() const;
+    bool commit_not_composited() const;
+    bool correct_dpi_with_display_size() const;
+    bool local_tonemap_screenshots() const;
+    bool override_trusted_overlay() const;
+    bool flush_buffer_slots_to_uncache() const;
+    bool force_compile_graphite_renderengine() const;
+    bool single_hop_screenshot() const;
+    bool trace_frame_rate_override() const;
+    bool true_hdr_screenshots() const;
 
 protected:
     // overridden for unit tests
diff --git a/services/surfaceflinger/common/include/common/test/FlagUtils.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
index 550c70d..5317cbb 100644
--- a/services/surfaceflinger/common/include/common/test/FlagUtils.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.h
@@ -18,7 +18,14 @@
 
 #include <common/FlagManager.h>
 
-#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
+// indirection to resolve __LINE__ in SET_FLAG_FOR_TEST, it's used to create a unique TestFlagSetter
+// setter var name everytime so multiple flags can be set in a test
+#define CONCAT_INNER(a, b) a##b
+#define CONCAT(a, b) CONCAT_INNER(a, b)
+#define SET_FLAG_FOR_TEST(name, value)            \
+    TestFlagSetter CONCAT(_testFlag_, __LINE__) { \
+        (name), (name), (value)                   \
+    }
 
 namespace android {
 class TestFlagSetter {
diff --git a/services/surfaceflinger/common/include/common/trace.h b/services/surfaceflinger/common/include/common/trace.h
new file mode 100644
index 0000000..dc5716b
--- /dev/null
+++ b/services/surfaceflinger/common/include/common/trace.h
@@ -0,0 +1,90 @@
+
+/*
+ * Copyright 2024 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
+
+#ifndef ATRACE_TAG
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#endif
+
+#include <cutils/trace.h>
+#include <tracing_perfetto.h>
+
+// prevent using atrace directly, calls should go through tracing_perfetto lib
+#undef ATRACE_ENABLED
+#undef ATRACE_BEGIN
+#undef ATRACE_END
+#undef ATRACE_ASYNC_BEGIN
+#undef ATRACE_ASYNC_END
+#undef ATRACE_ASYNC_FOR_TRACK_BEGIN
+#undef ATRACE_ASYNC_FOR_TRACK_END
+#undef ATRACE_INSTANT
+#undef ATRACE_INSTANT_FOR_TRACK
+#undef ATRACE_INT
+#undef ATRACE_INT64
+#undef ATRACE_CALL
+#undef ATRACE_NAME
+#undef ATRACE_FORMAT
+#undef ATRACE_FORMAT_INSTANT
+
+#define SFTRACE_ENABLED() ::tracing_perfetto::isTagEnabled(ATRACE_TAG)
+#define SFTRACE_BEGIN(name) ::tracing_perfetto::traceBegin(ATRACE_TAG, name)
+#define SFTRACE_END() ::tracing_perfetto::traceEnd(ATRACE_TAG)
+#define SFTRACE_ASYNC_BEGIN(name, cookie) \
+    ::tracing_perfetto::traceAsyncBegin(ATRACE_TAG, name, cookie)
+#define SFTRACE_ASYNC_END(name, cookie) ::tracing_perfetto::traceAsyncEnd(ATRACE_TAG, name, cookie)
+#define SFTRACE_ASYNC_FOR_TRACK_BEGIN(track_name, name, cookie) \
+    ::tracing_perfetto::traceAsyncBeginForTrack(ATRACE_TAG, name, track_name, cookie)
+#define SFTRACE_ASYNC_FOR_TRACK_END(track_name, cookie) \
+    ::tracing_perfetto::traceAsyncEndForTrack(ATRACE_TAG, track_name, cookie)
+#define SFTRACE_INSTANT(name) ::tracing_perfetto::traceInstant(ATRACE_TAG, name)
+#define SFTRACE_FORMAT_INSTANT(fmt, ...) \
+    ::tracing_perfetto::traceFormatInstant(ATRACE_TAG, fmt, ##__VA_ARGS__)
+#define SFTRACE_INSTANT_FOR_TRACK(trackName, name) \
+    ::tracing_perfetto::traceInstantForTrack(ATRACE_TAG, trackName, name)
+#define SFTRACE_INT(name, value) ::tracing_perfetto::traceCounter32(ATRACE_TAG, name, value)
+#define SFTRACE_INT64(name, value) ::tracing_perfetto::traceCounter(ATRACE_TAG, name, value)
+
+// SFTRACE_NAME traces from its location until the end of its enclosing scope.
+#define _PASTE(x, y) x##y
+#define PASTE(x, y) _PASTE(x, y)
+#define SFTRACE_NAME(name) ::android::ScopedTrace PASTE(___tracer, __LINE__)(name)
+// SFTRACE_CALL is an SFTRACE_NAME that uses the current function name.
+#define SFTRACE_CALL() SFTRACE_NAME(__FUNCTION__)
+
+#define SFTRACE_FORMAT(fmt, ...) \
+    ::android::ScopedTrace PASTE(___tracer, __LINE__)(fmt, ##__VA_ARGS__)
+
+#define ALOGE_AND_TRACE(fmt, ...)                   \
+    do {                                            \
+        ALOGE(fmt, ##__VA_ARGS__);                  \
+        SFTRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \
+    } while (false)
+
+namespace android {
+
+class ScopedTrace {
+public:
+    template <typename... Args>
+    inline ScopedTrace(const char* fmt, Args&&... args) {
+        ::tracing_perfetto::traceFormatBegin(ATRACE_TAG, fmt, std::forward<Args>(args)...);
+    }
+    inline ScopedTrace(const char* name) { SFTRACE_BEGIN(name); }
+    inline ~ScopedTrace() { SFTRACE_END(); }
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp
index f77b137..0a69a72 100644
--- a/services/surfaceflinger/layerproto/Android.bp
+++ b/services/surfaceflinger/layerproto/Android.bp
@@ -8,14 +8,9 @@
     default_team: "trendy_team_android_core_graphics_stack",
 }
 
-cc_library {
-    name: "liblayers_proto",
+cc_defaults {
+    name: "libsurfaceflinger_proto_deps",
     export_include_dirs: ["include"],
-
-    srcs: [
-        "LayerProtoParser.cpp",
-    ],
-
     static_libs: [
         "libperfetto_client_experimental",
     ],
@@ -31,24 +26,15 @@
     ],
 
     shared_libs: [
-        "android.hardware.graphics.common@1.1",
-        "libgui",
-        "libui",
         "libprotobuf-cpp-lite",
-        "libbase",
     ],
 
-    cppflags: [
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wno-format",
-        "-Wno-c++98-compat-pedantic",
-        "-Wno-float-conversion",
-        "-Wno-disabled-macro-expansion",
-        "-Wno-float-equal",
-        "-Wno-sign-conversion",
-        "-Wno-padded",
-        "-Wno-old-style-cast",
-        "-Wno-undef",
+    header_libs: [
+        "libsurfaceflinger_proto_headers",
     ],
 }
+
+cc_library_headers {
+    name: "libsurfaceflinger_proto_headers",
+    export_include_dirs: ["include"],
+}
diff --git a/services/surfaceflinger/layerproto/LayerProtoParser.cpp b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
deleted file mode 100644
index c3d0a40..0000000
--- a/services/surfaceflinger/layerproto/LayerProtoParser.cpp
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <android-base/stringprintf.h>
-#include <layerproto/LayerProtoParser.h>
-#include <ui/DebugUtils.h>
-
-using android::base::StringAppendF;
-using android::base::StringPrintf;
-
-namespace android {
-namespace surfaceflinger {
-
-bool sortLayers(LayerProtoParser::Layer* lhs, const LayerProtoParser::Layer* rhs) {
-    uint32_t ls = lhs->layerStack;
-    uint32_t rs = rhs->layerStack;
-    if (ls != rs) return ls < rs;
-
-    int32_t lz = lhs->z;
-    int32_t rz = rhs->z;
-    if (lz != rz) {
-        return lz < rz;
-    }
-
-    return lhs->id < rhs->id;
-}
-
-LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(
-        const perfetto::protos::LayersProto& layersProto) {
-    LayerTree layerTree;
-    layerTree.allLayers = generateLayerList(layersProto);
-
-    // find and sort the top-level layers
-    for (Layer& layer : layerTree.allLayers) {
-        if (layer.parent == nullptr) {
-            layerTree.topLevelLayers.push_back(&layer);
-        }
-    }
-    std::sort(layerTree.topLevelLayers.begin(), layerTree.topLevelLayers.end(), sortLayers);
-
-    return layerTree;
-}
-
-std::vector<LayerProtoParser::Layer> LayerProtoParser::generateLayerList(
-        const perfetto::protos::LayersProto& layersProto) {
-    std::vector<Layer> layerList;
-    std::unordered_map<int32_t, Layer*> layerMap;
-
-    // build the layer list and the layer map
-    layerList.reserve(layersProto.layers_size());
-    layerMap.reserve(layersProto.layers_size());
-    for (int i = 0; i < layersProto.layers_size(); i++) {
-        layerList.emplace_back(generateLayer(layersProto.layers(i)));
-        // this works because layerList never changes capacity
-        layerMap[layerList.back().id] = &layerList.back();
-    }
-
-    // fix up children and relatives
-    for (int i = 0; i < layersProto.layers_size(); i++) {
-        updateChildrenAndRelative(layersProto.layers(i), layerMap);
-    }
-
-    return layerList;
-}
-
-LayerProtoParser::Layer LayerProtoParser::generateLayer(
-        const perfetto::protos::LayerProto& layerProto) {
-    Layer layer;
-    layer.id = layerProto.id();
-    layer.name = layerProto.name();
-    layer.type = layerProto.type();
-    layer.transparentRegion = generateRegion(layerProto.transparent_region());
-    layer.visibleRegion = generateRegion(layerProto.visible_region());
-    layer.damageRegion = generateRegion(layerProto.damage_region());
-    layer.layerStack = layerProto.layer_stack();
-    layer.z = layerProto.z();
-    layer.position = {layerProto.position().x(), layerProto.position().y()};
-    layer.requestedPosition = {layerProto.requested_position().x(),
-                                layerProto.requested_position().y()};
-    layer.size = {layerProto.size().w(), layerProto.size().h()};
-    layer.crop = generateRect(layerProto.crop());
-    layer.isOpaque = layerProto.is_opaque();
-    layer.invalidate = layerProto.invalidate();
-    layer.dataspace = layerProto.dataspace();
-    layer.pixelFormat = layerProto.pixel_format();
-    layer.color = {layerProto.color().r(), layerProto.color().g(), layerProto.color().b(),
-                    layerProto.color().a()};
-    layer.requestedColor = {layerProto.requested_color().r(), layerProto.requested_color().g(),
-                             layerProto.requested_color().b(), layerProto.requested_color().a()};
-    layer.flags = layerProto.flags();
-    layer.transform = generateTransform(layerProto.transform());
-    layer.requestedTransform = generateTransform(layerProto.requested_transform());
-    layer.activeBuffer = generateActiveBuffer(layerProto.active_buffer());
-    layer.bufferTransform = generateTransform(layerProto.buffer_transform());
-    layer.queuedFrames = layerProto.queued_frames();
-    layer.refreshPending = layerProto.refresh_pending();
-    layer.isProtected = layerProto.is_protected();
-    layer.isTrustedOverlay = layerProto.is_trusted_overlay();
-    layer.cornerRadius = layerProto.corner_radius();
-    layer.backgroundBlurRadius = layerProto.background_blur_radius();
-    for (const auto& entry : layerProto.metadata()) {
-        const std::string& dataStr = entry.second;
-        std::vector<uint8_t>& outData = layer.metadata.mMap[entry.first];
-        outData.resize(dataStr.size());
-        memcpy(outData.data(), dataStr.data(), dataStr.size());
-    }
-    layer.cornerRadiusCrop = generateFloatRect(layerProto.corner_radius_crop());
-    layer.shadowRadius = layerProto.shadow_radius();
-    layer.ownerUid = layerProto.owner_uid();
-    return layer;
-}
-
-LayerProtoParser::Region LayerProtoParser::generateRegion(
-        const perfetto::protos::RegionProto& regionProto) {
-    LayerProtoParser::Region region;
-    for (int i = 0; i < regionProto.rect_size(); i++) {
-        const perfetto::protos::RectProto& rectProto = regionProto.rect(i);
-        region.rects.push_back(generateRect(rectProto));
-    }
-
-    return region;
-}
-
-LayerProtoParser::Rect LayerProtoParser::generateRect(
-        const perfetto::protos::RectProto& rectProto) {
-    LayerProtoParser::Rect rect;
-    rect.left = rectProto.left();
-    rect.top = rectProto.top();
-    rect.right = rectProto.right();
-    rect.bottom = rectProto.bottom();
-
-    return rect;
-}
-
-LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(
-        const perfetto::protos::FloatRectProto& rectProto) {
-    LayerProtoParser::FloatRect rect;
-    rect.left = rectProto.left();
-    rect.top = rectProto.top();
-    rect.right = rectProto.right();
-    rect.bottom = rectProto.bottom();
-
-    return rect;
-}
-
-LayerProtoParser::Transform LayerProtoParser::generateTransform(
-        const perfetto::protos::TransformProto& transformProto) {
-    LayerProtoParser::Transform transform;
-    transform.dsdx = transformProto.dsdx();
-    transform.dtdx = transformProto.dtdx();
-    transform.dsdy = transformProto.dsdy();
-    transform.dtdy = transformProto.dtdy();
-
-    return transform;
-}
-
-LayerProtoParser::ActiveBuffer LayerProtoParser::generateActiveBuffer(
-        const perfetto::protos::ActiveBufferProto& activeBufferProto) {
-    LayerProtoParser::ActiveBuffer activeBuffer;
-    activeBuffer.width = activeBufferProto.width();
-    activeBuffer.height = activeBufferProto.height();
-    activeBuffer.stride = activeBufferProto.stride();
-    activeBuffer.format = activeBufferProto.format();
-
-    return activeBuffer;
-}
-
-void LayerProtoParser::updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
-                                                 std::unordered_map<int32_t, Layer*>& layerMap) {
-    auto currLayer = layerMap[layerProto.id()];
-
-    for (int i = 0; i < layerProto.children_size(); i++) {
-        if (layerMap.count(layerProto.children(i)) > 0) {
-            currLayer->children.push_back(layerMap[layerProto.children(i)]);
-        }
-    }
-
-    for (int i = 0; i < layerProto.relatives_size(); i++) {
-        if (layerMap.count(layerProto.relatives(i)) > 0) {
-            currLayer->relatives.push_back(layerMap[layerProto.relatives(i)]);
-        }
-    }
-
-    if (layerProto.has_parent()) {
-        if (layerMap.count(layerProto.parent()) > 0) {
-            currLayer->parent = layerMap[layerProto.parent()];
-        }
-    }
-
-    if (layerProto.has_z_order_relative_of()) {
-        if (layerMap.count(layerProto.z_order_relative_of()) > 0) {
-            currLayer->zOrderRelativeOf = layerMap[layerProto.z_order_relative_of()];
-        }
-    }
-}
-
-std::string LayerProtoParser::layerTreeToString(const LayerTree& layerTree) {
-    std::string result;
-    for (const LayerProtoParser::Layer* layer : layerTree.topLevelLayers) {
-        if (layer->zOrderRelativeOf != nullptr) {
-            continue;
-        }
-        result.append(layerToString(layer));
-    }
-
-    return result;
-}
-
-std::string LayerProtoParser::layerToString(const LayerProtoParser::Layer* layer) {
-    std::string result;
-
-    std::vector<Layer*> traverse(layer->relatives);
-    for (LayerProtoParser::Layer* child : layer->children) {
-        if (child->zOrderRelativeOf != nullptr) {
-            continue;
-        }
-
-        traverse.push_back(child);
-    }
-
-    std::sort(traverse.begin(), traverse.end(), sortLayers);
-
-    size_t i = 0;
-    for (; i < traverse.size(); i++) {
-        auto& relative = traverse[i];
-        if (relative->z >= 0) {
-            break;
-        }
-        result.append(layerToString(relative));
-    }
-    result.append(layer->to_string());
-    result.append("\n");
-    for (; i < traverse.size(); i++) {
-        auto& relative = traverse[i];
-        result.append(layerToString(relative));
-    }
-
-    return result;
-}
-
-std::string LayerProtoParser::ActiveBuffer::to_string() const {
-    return StringPrintf("[%4ux%4u:%4u,%s]", width, height, stride,
-                        decodePixelFormat(format).c_str());
-}
-
-std::string LayerProtoParser::Transform::to_string() const {
-    return StringPrintf("[%.2f, %.2f][%.2f, %.2f]", static_cast<double>(dsdx),
-                        static_cast<double>(dtdx), static_cast<double>(dsdy),
-                        static_cast<double>(dtdy));
-}
-
-std::string LayerProtoParser::Rect::to_string() const {
-    return StringPrintf("[%3d, %3d, %3d, %3d]", left, top, right, bottom);
-}
-
-std::string LayerProtoParser::FloatRect::to_string() const {
-    return StringPrintf("[%.2f, %.2f, %.2f, %.2f]", left, top, right, bottom);
-}
-
-std::string LayerProtoParser::Region::to_string(const char* what) const {
-    std::string result =
-            StringPrintf("  Region %s (this=%lx count=%d)\n", what, static_cast<unsigned long>(id),
-                         static_cast<int>(rects.size()));
-
-    for (auto& rect : rects) {
-        StringAppendF(&result, "    %s\n", rect.to_string().c_str());
-    }
-
-    return result;
-}
-
-std::string LayerProtoParser::Layer::to_string() const {
-    std::string result;
-    StringAppendF(&result, "+ %s (%s) uid=%d\n", type.c_str(), name.c_str(), ownerUid);
-    result.append(transparentRegion.to_string("TransparentRegion").c_str());
-    result.append(visibleRegion.to_string("VisibleRegion").c_str());
-    result.append(damageRegion.to_string("SurfaceDamageRegion").c_str());
-
-    StringAppendF(&result, "      layerStack=%4d, z=%9d, pos=(%g,%g), size=(%4d,%4d), ", layerStack,
-                  z, static_cast<double>(position.x), static_cast<double>(position.y), size.x,
-                  size.y);
-
-    StringAppendF(&result, "crop=%s, ", crop.to_string().c_str());
-    StringAppendF(&result, "cornerRadius=%f, ", cornerRadius);
-    StringAppendF(&result, "isProtected=%1d, ", isProtected);
-    StringAppendF(&result, "isTrustedOverlay=%1d, ", isTrustedOverlay);
-    StringAppendF(&result, "isOpaque=%1d, invalidate=%1d, ", isOpaque, invalidate);
-    StringAppendF(&result, "dataspace=%s, ", dataspace.c_str());
-    StringAppendF(&result, "defaultPixelFormat=%s, ", pixelFormat.c_str());
-    StringAppendF(&result, "backgroundBlurRadius=%1d, ", backgroundBlurRadius);
-    StringAppendF(&result, "color=(%.3f,%.3f,%.3f,%.3f), flags=0x%08x, ",
-                  static_cast<double>(color.r), static_cast<double>(color.g),
-                  static_cast<double>(color.b), static_cast<double>(color.a), flags);
-    StringAppendF(&result, "tr=%s", transform.to_string().c_str());
-    result.append("\n");
-    StringAppendF(&result, "      parent=%s\n", parent == nullptr ? "none" : parent->name.c_str());
-    StringAppendF(&result, "      zOrderRelativeOf=%s\n",
-                  zOrderRelativeOf == nullptr ? "none" : zOrderRelativeOf->name.c_str());
-    StringAppendF(&result, "      activeBuffer=%s,", activeBuffer.to_string().c_str());
-    StringAppendF(&result, " tr=%s", bufferTransform.to_string().c_str());
-    StringAppendF(&result, " queued-frames=%d", queuedFrames);
-    StringAppendF(&result, " metadata={");
-    bool first = true;
-    for (const auto& entry : metadata.mMap) {
-        if (!first) result.append(", ");
-        first = false;
-        result.append(metadata.itemToString(entry.first, ":"));
-    }
-    result.append("},");
-    StringAppendF(&result, " cornerRadiusCrop=%s, ", cornerRadiusCrop.to_string().c_str());
-    StringAppendF(&result, " shadowRadius=%.3f, ", shadowRadius);
-    return result;
-}
-
-} // namespace surfaceflinger
-} // namespace android
diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
deleted file mode 100644
index 79c3982..0000000
--- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <layerproto/LayerProtoHeader.h>
-
-#include <gui/LayerMetadata.h>
-#include <math/vec4.h>
-
-#include <memory>
-#include <unordered_map>
-#include <vector>
-
-using android::gui::LayerMetadata;
-
-namespace android {
-namespace surfaceflinger {
-
-class LayerProtoParser {
-public:
-    class ActiveBuffer {
-    public:
-        uint32_t width;
-        uint32_t height;
-        uint32_t stride;
-        int32_t format;
-
-        std::string to_string() const;
-    };
-
-    class Transform {
-    public:
-        float dsdx;
-        float dtdx;
-        float dsdy;
-        float dtdy;
-
-        std::string to_string() const;
-    };
-
-    class Rect {
-    public:
-        int32_t left;
-        int32_t top;
-        int32_t right;
-        int32_t bottom;
-
-        std::string to_string() const;
-    };
-
-    class FloatRect {
-    public:
-        float left;
-        float top;
-        float right;
-        float bottom;
-
-        std::string to_string() const;
-    };
-
-    class Region {
-    public:
-        uint64_t id;
-        std::vector<Rect> rects;
-
-        std::string to_string(const char* what) const;
-    };
-
-    class Layer {
-    public:
-        int32_t id;
-        std::string name;
-        std::vector<Layer*> children;
-        std::vector<Layer*> relatives;
-        std::string type;
-        LayerProtoParser::Region transparentRegion;
-        LayerProtoParser::Region visibleRegion;
-        LayerProtoParser::Region damageRegion;
-        uint32_t layerStack;
-        int32_t z;
-        float2 position;
-        float2 requestedPosition;
-        int2 size;
-        LayerProtoParser::Rect crop;
-        bool isOpaque;
-        bool invalidate;
-        std::string dataspace;
-        std::string pixelFormat;
-        half4 color;
-        half4 requestedColor;
-        uint32_t flags;
-        Transform transform;
-        Transform requestedTransform;
-        Layer* parent = 0;
-        Layer* zOrderRelativeOf = 0;
-        LayerProtoParser::ActiveBuffer activeBuffer;
-        Transform bufferTransform;
-        int32_t queuedFrames;
-        bool refreshPending;
-        bool isProtected;
-        bool isTrustedOverlay;
-        float cornerRadius;
-        int backgroundBlurRadius;
-        LayerMetadata metadata;
-        LayerProtoParser::FloatRect cornerRadiusCrop;
-        float shadowRadius;
-        uid_t ownerUid;
-
-        std::string to_string() const;
-    };
-
-    class LayerTree {
-    public:
-        // all layers in LayersProto and in the original order
-        std::vector<Layer> allLayers;
-
-        // pointers to top-level layers in allLayers
-        std::vector<Layer*> topLevelLayers;
-    };
-
-    static LayerTree generateLayerTree(const perfetto::protos::LayersProto& layersProto);
-    static std::string layerTreeToString(const LayerTree& layerTree);
-
-private:
-    static std::vector<Layer> generateLayerList(const perfetto::protos::LayersProto& layersProto);
-    static LayerProtoParser::Layer generateLayer(const perfetto::protos::LayerProto& layerProto);
-    static LayerProtoParser::Region generateRegion(
-            const perfetto::protos::RegionProto& regionProto);
-    static LayerProtoParser::Rect generateRect(const perfetto::protos::RectProto& rectProto);
-    static LayerProtoParser::FloatRect generateFloatRect(
-            const perfetto::protos::FloatRectProto& rectProto);
-    static LayerProtoParser::Transform generateTransform(
-            const perfetto::protos::TransformProto& transformProto);
-    static LayerProtoParser::ActiveBuffer generateActiveBuffer(
-            const perfetto::protos::ActiveBufferProto& activeBufferProto);
-    static void updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
-                                          std::unordered_map<int32_t, Layer*>& layerMap);
-
-    static std::string layerToString(const LayerProtoParser::Layer* layer);
-};
-
-} // namespace surfaceflinger
-} // namespace android
diff --git a/services/surfaceflinger/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
index 5174fa7..56bca7f 100644
--- a/services/surfaceflinger/surfaceflinger_flags.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -161,7 +161,7 @@
 flag {
   name: "graphite_renderengine"
   namespace: "core_graphics"
-  description: "Use Skia's Graphite Vulkan backend in RenderEngine."
+  description: "Compile AND enable Skia's Graphite Vulkan backend in RenderEngine. See also: force_compile_graphite_renderengine."
   bug: "293371537"
   is_fixed_read_only: true
 }
@@ -222,4 +222,15 @@
   }
 }
 
+flag {
+  name: "allow_n_vsyncs_in_targeter"
+  namespace: "core_graphics"
+  description: "This flag will enable utilizing N vsyncs in the FrameTargeter for past vsyncs"
+  bug: "308858993"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
 # This file is locked and should not be changed. Use surfaceflinger_flags_new.aconfig
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 5451752..102e2b6 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,10 +4,185 @@
 container: "system"
 
 flag {
-  name: "dont_skip_on_early_ro2"
+  name: "adpf_gpu_sf"
+  namespace: "game"
+  description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
+  bug: "284324521"
+} # adpf_gpu_sf
+
+flag {
+  name: "ce_fence_promise"
+  namespace: "window_surfaces"
+  description: "Moves logic for buffer release fences into LayerFE"
+  bug: "294936197"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # ce_fence_promise
+
+flag {
+  name: "commit_not_composited"
   namespace: "core_graphics"
-  description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
-  bug: "273702768"
-} # dont_skip_on_early_ro2
+  description: "mark frames as non janky if the transaction resulted in no composition"
+  bug: "340633280"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # commit_not_composited
+
+flag {
+  name: "correct_dpi_with_display_size"
+  namespace: "core_graphics"
+  description: "indicate whether missing or likely incorrect dpi should be corrected using the display size."
+  bug: "328425848"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # correct_dpi_with_display_size
+
+flag {
+  name: "deprecate_vsync_sf"
+  namespace: "core_graphics"
+  description: "Depracate eVsyncSourceSurfaceFlinger and use vsync_app everywhere"
+  bug: "162235855"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # deprecate_vsync_sf
+
+flag {
+  name: "detached_mirror"
+  namespace: "window_surfaces"
+  description: "Ignore local transform when mirroring a partial hierarchy"
+  bug: "337845753"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # detached_mirror
+
+flag {
+  name: "filter_frames_before_trace_starts"
+  namespace: "core_graphics"
+  description: "Do not trace FrameTimeline events for frames started before the trace started"
+  bug: "364194637"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # filter_frames_before_trace_starts
+
+flag {
+  name: "flush_buffer_slots_to_uncache"
+  namespace: "core_graphics"
+  description: "Flush DisplayCommands for disabled displays in order to uncache requested buffers."
+  bug: "330806421"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # flush_buffer_slots_to_uncache
+
+flag {
+  name: "force_compile_graphite_renderengine"
+  namespace: "core_graphics"
+  description: "Compile Skia's Graphite Vulkan backend in RenderEngine, but do NOT enable it, unless graphite_renderengine is also set. It can also be enabled with the debug.renderengine.graphite system property for testing. In contrast, the graphite_renderengine flag both compiles AND enables Graphite in RenderEngine."
+  bug: "293371537"
+  is_fixed_read_only: true
+} # force_compile_graphite_renderengine
+
+flag {
+  name: "frame_rate_category_mrr"
+  namespace: "core_graphics"
+  description: "Enable to use frame rate category and newer frame rate votes such as GTE in SurfaceFlinger scheduler, to guard dVRR changes from MRR devices"
+  bug: "330224639"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # frame_rate_category_mrr
+
+flag {
+  name: "latch_unsignaled_with_auto_refresh_changed"
+  namespace: "core_graphics"
+  description: "Ignore eAutoRefreshChanged with latch unsignaled"
+  bug: "331513837"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # latch_unsignaled_with_auto_refresh_changed
+
+flag {
+  name: "local_tonemap_screenshots"
+  namespace: "core_graphics"
+  description: "Enables local tonemapping when capturing screenshots"
+  bug: "329464641"
+  is_fixed_read_only: true
+} # local_tonemap_screenshots
+
+flag {
+  name: "single_hop_screenshot"
+  namespace: "window_surfaces"
+  description: "Only access SF main thread once during a screenshot"
+  bug: "285553970"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+ } # single_hop_screenshot
+
+flag {
+  name: "true_hdr_screenshots"
+  namespace: "core_graphics"
+  description: "Enables screenshotting display content in HDR, sans tone mapping"
+  bug: "329470026"
+  is_fixed_read_only: true
+} # true_hdr_screenshots
+
+ flag {
+  name: "override_trusted_overlay"
+  namespace: "window_surfaces"
+  description: "Allow child to disable trusted overlay set by a parent layer"
+  bug: "339701674"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # override_trusted_overlay
+
+flag {
+  name: "view_set_requested_frame_rate_mrr"
+  namespace: "core_graphics"
+  description: "Enable to use frame rate category NoPreference with fixed frame rate vote on MRR devices"
+  bug: "352206100"
+  is_fixed_read_only: true
+} # view_set_requested_frame_rate_mrr
+
+flag {
+  name: "vrr_bugfix_24q4"
+  namespace: "core_graphics"
+  description: "bug fixes for VRR"
+  bug: "331513837"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_24q4
+
+flag {
+  name: "vrr_bugfix_dropped_frame"
+  namespace: "core_graphics"
+  description: "bug fix for VRR dropped frame"
+  bug: "343603085"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # vrr_bugfix_dropped_frame
 
 # IMPORTANT - please keep alphabetize to reduce merge conflicts
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index dab0a3f..4d5c0fd 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -27,6 +27,8 @@
     defaults: [
         "android.hardware.graphics.common-ndk_shared",
         "surfaceflinger_defaults",
+        "libsurfaceflinger_common_test_deps",
+        "libsurfaceflinger_proto_deps",
     ],
     test_suites: ["device-tests"],
     srcs: [
@@ -37,10 +39,10 @@
         "DereferenceSurfaceControl_test.cpp",
         "DisplayConfigs_test.cpp",
         "DisplayEventReceiver_test.cpp",
+        "Dumpsys_test.cpp",
         "EffectLayer_test.cpp",
         "HdrSdrRatioOverlay_test.cpp",
         "InvalidHandles_test.cpp",
-        "LayerBorder_test.cpp",
         "LayerCallback_test.cpp",
         "LayerRenderTypeTransaction_test.cpp",
         "LayerState_test.cpp",
@@ -57,16 +59,14 @@
         "ScreenCapture_test.cpp",
         "SetFrameRate_test.cpp",
         "SetGeometry_test.cpp",
-        "Stress_test.cpp",
         "TextureFiltering_test.cpp",
         "VirtualDisplay_test.cpp",
         "WindowInfosListener_test.cpp",
     ],
     data: ["SurfaceFlinger_test.filter"],
     static_libs: [
-        "liblayers_proto",
         "android.hardware.graphics.composer@2.1",
-        "libsurfaceflingerflags",
+        "libsurfaceflinger_common",
     ],
     shared_libs: [
         "android.hardware.graphics.common@1.2",
@@ -82,6 +82,7 @@
         "libprotobuf-cpp-full",
         "libui",
         "libutils",
+        "server_configurable_flags",
     ],
     header_libs: [
         "libnativewindow_headers",
@@ -119,7 +120,6 @@
         "libEGL",
         "libGLESv2",
         "libgui",
-        "liblayers_proto",
         "liblog",
         "libprotobuf-cpp-full",
         "libui",
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
index 4f41a81..222642f 100644
--- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -18,7 +18,7 @@
 
 #include <gtest/gtest.h>
 
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
diff --git a/services/surfaceflinger/tests/BufferGenerator.cpp b/services/surfaceflinger/tests/BufferGenerator.cpp
index d74bd55..efab7b8 100644
--- a/services/surfaceflinger/tests/BufferGenerator.cpp
+++ b/services/surfaceflinger/tests/BufferGenerator.cpp
@@ -42,8 +42,13 @@
      * through saved callback. */
     class BufferListener : public ConsumerBase::FrameAvailableListener {
     public:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        BufferListener(sp<BufferItemConsumer> consumer, BufferCallback callback)
+#else
         BufferListener(sp<IGraphicBufferConsumer> consumer, BufferCallback callback)
-              : mConsumer(consumer), mCallback(callback) {}
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+              : mConsumer(consumer), mCallback(callback) {
+        }
 
         void onFrameAvailable(const BufferItem& /*item*/) {
             BufferItem item;
@@ -55,7 +60,11 @@
         }
 
     private:
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        sp<BufferItemConsumer> mConsumer;
+#else
         sp<IGraphicBufferConsumer> mConsumer;
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
         BufferCallback mCallback;
     };
 
@@ -63,6 +72,16 @@
      * queue. */
     void initialize(uint32_t width, uint32_t height, android_pixel_format_t format,
                     BufferCallback callback) {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        mBufferItemConsumer = sp<BufferItemConsumer>::make(GraphicBuffer::USAGE_HW_TEXTURE);
+        mBufferItemConsumer->setDefaultBufferSize(width, height);
+        mBufferItemConsumer->setDefaultBufferFormat(format);
+
+        mListener = sp<BufferListener>::make(mBufferItemConsumer, callback);
+        mBufferItemConsumer->setFrameAvailableListener(mListener);
+
+        mSurface = mBufferItemConsumer->getSurface();
+#else
         sp<IGraphicBufferProducer> producer;
         sp<IGraphicBufferConsumer> consumer;
         BufferQueue::createBufferQueue(&producer, &consumer);
@@ -77,6 +96,7 @@
         mBufferItemConsumer->setFrameAvailableListener(mListener);
 
         mSurface = sp<Surface>::make(producer, true);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     /* Used by Egl manager. The surface is never displayed. */
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 822ac4d..e6fed63 100644
--- a/services/surfaceflinger/tests/Credentials_test.cpp
+++ b/services/surfaceflinger/tests/Credentials_test.cpp
@@ -20,13 +20,13 @@
 
 #include <android/gui/ISurfaceComposer.h>
 #include <gtest/gtest.h>
-#include <gui/AidlStatusUtil.h>
-#include <gui/LayerDebugInfo.h>
+#include <gui/AidlUtil.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/android_filesystem_config.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
 #include <ui/DynamicDisplayInfo.h>
 #include <utils/String8.h>
 #include <functional>
@@ -36,13 +36,12 @@
 namespace android {
 
 using Transaction = SurfaceComposerClient::Transaction;
-using gui::LayerDebugInfo;
 using gui::aidl_utils::statusTFromBinderStatus;
 using ui::ColorMode;
 
 namespace {
-const String8 DISPLAY_NAME("Credentials Display Test");
-const String8 SURFACE_NAME("Test Surface Name");
+const std::string kDisplayName("Credentials Display Test");
+const String8 kSurfaceName("Test Surface Name");
 } // namespace
 
 /**
@@ -101,7 +100,7 @@
         ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(mDisplay, &mode));
 
         // Background surface
-        mBGSurfaceControl = mComposerClient->createSurface(SURFACE_NAME, mode.resolution.getWidth(),
+        mBGSurfaceControl = mComposerClient->createSurface(kSurfaceName, mode.resolution.getWidth(),
                                                            mode.resolution.getHeight(),
                                                            PIXEL_FORMAT_RGBA_8888, 0);
         ASSERT_TRUE(mBGSurfaceControl != nullptr);
@@ -234,14 +233,14 @@
 TEST_F(CredentialsTest, CreateDisplayTest) {
     // Only graphics and system processes can create a secure display.
     std::function<bool()> condition = [=]() {
-        sp<IBinder> testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, true);
+        sp<IBinder> testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, true);
         return testDisplay.get() != nullptr;
     };
 
     // Check with root.
     {
         UIDFaker f(AID_ROOT);
-        ASSERT_FALSE(condition());
+        ASSERT_TRUE(condition());
     }
 
     // Check as a Graphics user.
@@ -269,7 +268,7 @@
     }
 
     condition = [=]() {
-        sp<IBinder> testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false);
+        sp<IBinder> testDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName, false);
         return testDisplay.get() != nullptr;
     };
     ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
@@ -278,10 +277,10 @@
 TEST_F(CredentialsTest, CaptureLayersTest) {
     setupBackgroundSurface();
     sp<GraphicBuffer> outBuffer;
-    std::function<status_t()> condition = [=]() {
+    std::function<status_t()> condition = [=, this]() {
         LayerCaptureArgs captureArgs;
         captureArgs.layerHandle = mBGSurfaceControl->getHandle();
-        captureArgs.sourceCrop = {0, 0, 1, 1};
+        captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(0, 0, 1, 1);
 
         ScreenCaptureResults captureResults;
         return ScreenCapture::captureLayers(captureArgs, captureResults);
@@ -292,35 +291,6 @@
 /**
  * The following tests are for methods accessible directly through SurfaceFlinger.
  */
-TEST_F(CredentialsTest, GetLayerDebugInfo) {
-    setupBackgroundSurface();
-    sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
-
-    // Historically, only root and shell can access the getLayerDebugInfo which
-    // is called when we call dumpsys. I don't see a reason why we should change this.
-    std::vector<LayerDebugInfo> outLayers;
-    binder::Status status = binder::Status::ok();
-    // Check with root.
-    {
-        UIDFaker f(AID_ROOT);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
-
-    // Check as a shell.
-    {
-        UIDFaker f(AID_SHELL);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
-    }
-
-    // Check as anyone else.
-    {
-        UIDFaker f(AID_BIN);
-        status = sf->getLayerDebugInfo(&outLayers);
-        ASSERT_EQ(PERMISSION_DENIED, statusTFromBinderStatus(status));
-    }
-}
 
 TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) {
     const auto display = getFirstDisplayToken();
@@ -389,8 +359,13 @@
                 .apply();
     }
 
-    // Called from non privileged process
-    Transaction().setTrustedOverlay(surfaceControl, true);
+    // Attempt to set a trusted overlay from a non-privileged process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
+    }
+
+    // Verify that the layer was not made a trusted overlay.
     {
         UIDFaker f(AID_SYSTEM);
         auto windowIsPresentAndNotTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
@@ -401,12 +376,14 @@
             }
             return !foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndNotTrusted));
     }
 
+    // Verify that privileged processes are able to set trusted overlays.
     {
         UIDFaker f(AID_SYSTEM);
-        Transaction().setTrustedOverlay(surfaceControl, true);
+        Transaction().setTrustedOverlay(surfaceControl, true).apply(/*synchronous=*/true);
         auto windowIsPresentAndTrusted = [&](const std::vector<WindowInfo>& windowInfos) {
             auto foundWindowInfo =
                     WindowInfosListenerUtils::findMatchingWindowInfo(windowInfo, windowInfos);
@@ -415,10 +392,61 @@
             }
             return foundWindowInfo->inputConfig.test(WindowInfo::InputConfig::TRUSTED_OVERLAY);
         };
-        windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted);
+        ASSERT_TRUE(
+                windowInfosListenerUtils.waitForWindowInfosPredicate(windowIsPresentAndTrusted));
     }
 }
 
+TEST_F(CredentialsTest, DisplayTransactionPermissionTest) {
+    const auto display = getFirstDisplayToken();
+
+    ui::DisplayState displayState;
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    const ui::Rotation initialOrientation = displayState.orientation;
+
+    // Set display orientation from an untrusted process. This should fail silently.
+    {
+        UIDFaker f{AID_BIN};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did not change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+
+    // Set display orientation from a trusted process.
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation + ui::ROTATION_90,
+                                         layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+
+    // Verify that the display orientation did change.
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation + ui::ROTATION_90, displayState.orientation);
+
+    // Reset orientation
+    {
+        UIDFaker f{AID_SYSTEM};
+        Transaction transaction;
+        Rect layerStackRect;
+        Rect displayRect;
+        transaction.setDisplayProjection(display, initialOrientation, layerStackRect, displayRect);
+        transaction.apply(/*synchronous=*/true);
+    }
+    ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getDisplayState(display, &displayState));
+    ASSERT_EQ(initialOrientation, displayState.orientation);
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/Dumpsys_test.cpp b/services/surfaceflinger/tests/Dumpsys_test.cpp
new file mode 100644
index 0000000..c3914e5
--- /dev/null
+++ b/services/surfaceflinger/tests/Dumpsys_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/native_window.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include "android-base/stringprintf.h"
+#include "utils/Errors.h"
+
+namespace android {
+
+namespace {
+status_t runShellCommand(const std::string& cmd, std::string& result) {
+    FILE* pipe = popen(cmd.c_str(), "r");
+    if (!pipe) {
+        return UNKNOWN_ERROR;
+    }
+
+    char buffer[1024];
+    while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
+        result += buffer;
+    }
+
+    pclose(pipe);
+    return OK;
+}
+} // namespace
+
+using android::hardware::graphics::common::V1_1::BufferUsage;
+
+class WaitForCompletedCallback {
+public:
+    WaitForCompletedCallback() = default;
+    ~WaitForCompletedCallback() = default;
+
+    static void transactionCompletedCallback(void* callbackContext, nsecs_t /* latchTime */,
+                                             const sp<Fence>& /* presentFence */,
+                                             const std::vector<SurfaceControlStats>& /* stats */) {
+        ASSERT_NE(callbackContext, nullptr) << "failed to get callback context";
+        WaitForCompletedCallback* context = static_cast<WaitForCompletedCallback*>(callbackContext);
+        context->notify();
+    }
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+    }
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST(Dumpsys, listLayers) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    std::string layersAsString;
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --list", layersAsString));
+    EXPECT_NE(strstr(layersAsString.c_str(), ""), nullptr);
+}
+
+TEST(Dumpsys, stats) {
+    sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
+    ASSERT_EQ(NO_ERROR, client->initCheck());
+    auto newLayer =
+            client->createSurface(String8("MY_TEST_LAYER"), 100, 100, PIXEL_FORMAT_RGBA_8888, 0);
+    uint64_t usageFlags = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
+            BufferUsage::COMPOSER_OVERLAY | BufferUsage::GPU_TEXTURE;
+
+    sp<GraphicBuffer> buffer =
+            sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, 1u, usageFlags, "test");
+
+    WaitForCompletedCallback callback;
+    SurfaceComposerClient::Transaction()
+            .setBuffer(newLayer, buffer)
+            .addTransactionCompletedCallback(WaitForCompletedCallback::transactionCompletedCallback,
+                                             &callback)
+            .apply();
+    callback.wait();
+    std::string stats;
+    std::string layerName = base::StringPrintf("MY_TEST_LAYER#%d", newLayer->getLayerId());
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency " + layerName, stats));
+    EXPECT_NE(std::string(""), stats);
+    EXPECT_EQ(OK, runShellCommand("dumpsys SurfaceFlinger --latency-clear " + layerName, stats));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/LayerBorder_test.cpp b/services/surfaceflinger/tests/LayerBorder_test.cpp
deleted file mode 100644
index 00e134b..0000000
--- a/services/surfaceflinger/tests/LayerBorder_test.cpp
+++ /dev/null
@@ -1,287 +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.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-// TODO: Amend all tests when screenshots become fully reworked for borders
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <chrono> // std::chrono::seconds
-#include <thread> // std::this_thread::sleep_for
-#include "LayerTransactionTest.h"
-
-namespace android {
-
-class LayerBorderTest : public LayerTransactionTest {
-protected:
-    virtual void SetUp() {
-        LayerTransactionTest::SetUp();
-        ASSERT_EQ(NO_ERROR, mClient->initCheck());
-
-        toHalf3 = ColorTransformHelper::toHalf3;
-        toHalf4 = ColorTransformHelper::toHalf4;
-
-        const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
-        ASSERT_FALSE(ids.empty());
-        const auto display = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        ASSERT_FALSE(display == nullptr);
-        mColorOrange = toHalf4({255, 140, 0, 255});
-        mParentLayer = createColorLayer("Parent layer", Color::RED);
-
-        mContainerLayer = mClient->createSurface(String8("Container Layer"), 0 /* width */,
-                                                 0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                                 ISurfaceComposerClient::eFXSurfaceContainer |
-                                                         ISurfaceComposerClient::eNoColorFill,
-                                                 mParentLayer->getHandle());
-        EXPECT_NE(nullptr, mContainerLayer.get()) << "failed to create container layer";
-
-        mEffectLayer1 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-        EXPECT_NE(nullptr, mEffectLayer1.get()) << "failed to create effect layer 1";
-
-        mEffectLayer2 = mClient->createSurface(String8("Effect Layer"), 0 /* width */,
-                                               0 /* height */, PIXEL_FORMAT_RGBA_8888,
-                                               ISurfaceComposerClient::eFXSurfaceEffect |
-                                                       ISurfaceComposerClient::eNoColorFill,
-                                               mContainerLayer->getHandle());
-
-        EXPECT_NE(nullptr, mEffectLayer2.get()) << "failed to create effect layer 2";
-
-        asTransaction([&](Transaction& t) {
-            t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-            t.setLayer(mParentLayer, INT32_MAX - 20).show(mParentLayer);
-            t.setFlags(mParentLayer, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
-
-            t.setColor(mEffectLayer1, toHalf3(Color::BLUE));
-
-            t.setColor(mEffectLayer2, toHalf3(Color::GREEN));
-        });
-    }
-
-    virtual void TearDown() {
-        // Uncomment the line right below when running any of the tests
-        // std::this_thread::sleep_for (std::chrono::seconds(30));
-        LayerTransactionTest::TearDown();
-        mParentLayer = 0;
-    }
-
-    std::function<half3(Color)> toHalf3;
-    std::function<half4(Color)> toHalf4;
-    sp<SurfaceControl> mParentLayer, mContainerLayer, mEffectLayer1, mEffectLayer2;
-    half4 mColorOrange;
-};
-
-TEST_F(LayerBorderTest, OverlappingVisibleRegions) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, PartiallyCoveredVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, NonOverlappingVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, EmptyVisibleRegion) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(200, 200, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(0, 0, 600, 600));
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, ZOrderAdjustment) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setLayer(mParentLayer, 10);
-        t.setLayer(mEffectLayer1, 30);
-        t.setLayer(mEffectLayer2, 20);
-
-        t.enableBorder(mEffectLayer1, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, GrandChildHierarchy) {
-    sp<SurfaceControl> containerLayer2 =
-            mClient->createSurface(String8("Container Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceContainer |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   mContainerLayer->getHandle());
-    EXPECT_NE(nullptr, containerLayer2.get()) << "failed to create container layer 2";
-
-    sp<SurfaceControl> effectLayer3 =
-            mClient->createSurface(String8("Effect Layer"), 0 /* width */, 0 /* height */,
-                                   PIXEL_FORMAT_RGBA_8888,
-                                   ISurfaceComposerClient::eFXSurfaceEffect |
-                                           ISurfaceComposerClient::eNoColorFill,
-                                   containerLayer2->getHandle());
-
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setCrop(effectLayer3, Rect(400, 400, 800, 800));
-        t.setColor(effectLayer3, toHalf3(Color::BLUE));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(effectLayer3);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, TransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer1, 0.0f);
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, SemiTransparentAlpha) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-        t.setAlpha(mEffectLayer2, 0.5f);
-
-        t.enableBorder(mEffectLayer2, true, 20, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, InvisibleLayers) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, LayerWithBuffer) {
-    asTransaction([&](Transaction& t) {
-        t.hide(mEffectLayer1);
-        t.hide(mEffectLayer2);
-        t.show(mContainerLayer);
-
-        sp<SurfaceControl> layer =
-                mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
-                                       PIXEL_FORMAT_RGBA_8888,
-                                       ISurfaceComposerClient::eFXSurfaceBufferState,
-                                       mContainerLayer->getHandle());
-
-        sp<GraphicBuffer> buffer =
-                sp<GraphicBuffer>::make(400u, 400u, PIXEL_FORMAT_RGBA_8888, 1u,
-                                        BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN |
-                                                BufferUsage::COMPOSER_OVERLAY |
-                                                BufferUsage::GPU_TEXTURE,
-                                        "test");
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 200, 200), Color::GREEN);
-        TransactionUtils::fillGraphicBufferColor(buffer, Rect(200, 200, 400, 400), Color::BLUE);
-
-        t.setBuffer(layer, buffer);
-        t.setPosition(layer, 100, 100);
-        t.show(layer);
-        t.enableBorder(mContainerLayer, true, 20, mColorOrange);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidth) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 50, mColorOrange);
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomColor) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 400, 400));
-        t.setCrop(mEffectLayer2, Rect(200, 200, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 20, toHalf4({255, 0, 255, 255}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-TEST_F(LayerBorderTest, CustomWidthAndColorAndOpacity) {
-    asTransaction([&](Transaction& t) {
-        t.setCrop(mEffectLayer1, Rect(0, 0, 200, 200));
-        t.setCrop(mEffectLayer2, Rect(400, 400, 600, 600));
-
-        t.enableBorder(mContainerLayer, true, 40, toHalf4({255, 255, 0, 128}));
-        t.show(mEffectLayer1);
-        t.show(mEffectLayer2);
-        t.show(mContainerLayer);
-    });
-}
-
-} // namespace android
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/LayerCallback_test.cpp b/services/surfaceflinger/tests/LayerCallback_test.cpp
index 79886bd..b4496d3 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -1295,4 +1295,74 @@
     }
 }
 
+TEST_F(LayerCallbackTest, OccludedLayerHasReleaseCallback) {
+    sp<SurfaceControl> layer1, layer2;
+    ASSERT_NO_FATAL_FAILURE(layer1 = createLayerWithBuffer());
+    ASSERT_NO_FATAL_FAILURE(layer2 = createLayerWithBuffer());
+
+    Transaction transaction1, transaction2;
+    CallbackHelper callback1a, callback1b, callback2a, callback2b;
+    int err = fillTransaction(transaction1, &callback1a, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2a, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    ui::Size bufferSize = getBufferSize();
+
+    // Occlude layer1 with layer2
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    ExpectedResult expected1a, expected1b, expected2a, expected2b;
+    expected1a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    expected2a.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::NOT_RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1a, expected1a, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2a, expected2a, true));
+
+    // Submit new buffers so previous buffers can be released
+    err = fillTransaction(transaction1, &callback1b, layer1);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+    err = fillTransaction(transaction2, &callback2b, layer2);
+    if (err) {
+        GTEST_SUCCEED() << "test not supported";
+        return;
+    }
+
+    TransactionUtils::setFrame(transaction1, layer1,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    TransactionUtils::setFrame(transaction2, layer2,
+                               Rect(0, 0, bufferSize.width, bufferSize.height), Rect(0, 0, 32, 32));
+    transaction1.apply();
+    transaction2.apply();
+
+    expected1b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer1},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    expected2b.addSurface(ExpectedResult::Transaction::PRESENTED, {layer2},
+                          ExpectedResult::Buffer::ACQUIRED,
+                          ExpectedResult::PreviousBuffer::RELEASED);
+
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback1b, expected1b, true));
+    EXPECT_NO_FATAL_FAILURE(waitForCallback(callback2b, expected2b, true));
+}
 } // namespace android
diff --git a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
index 2b1834d..4b3ad8a 100644
--- a/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp
@@ -154,8 +154,6 @@
 
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction().setPosition(layerG, 16, 16).setRelativeLayer(layerG, layerR, 1).apply();
             break;
@@ -200,13 +198,6 @@
     // layerR = 0, layerG = layerR + 3, layerB = 2
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction()
-                    .setPosition(layerG, 8, 8)
-                    .setRelativeLayer(layerG, layerR, 3)
-                    .setPosition(layerB, 16, 16)
-                    .setLayer(layerB, mLayerZBase + 2)
-                    .apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction()
                     .setPosition(layerG, 8, 8)
@@ -413,13 +404,6 @@
 
     switch (layerType) {
         case ISurfaceComposerClient::eFXSurfaceBufferQueue:
-            Transaction()
-                    .setAlpha(layer1, 0.25f)
-                    .setAlpha(layer2, 0.75f)
-                    .setPosition(layer2, 16, 0)
-                    .setLayer(layer2, mLayerZBase + 1)
-                    .apply();
-            break;
         case ISurfaceComposerClient::eFXSurfaceBufferState:
             Transaction()
                     .setAlpha(layer1, 0.25f)
diff --git a/services/surfaceflinger/tests/LayerState_test.cpp b/services/surfaceflinger/tests/LayerState_test.cpp
index 15a98df..cc57e11 100644
--- a/services/surfaceflinger/tests/LayerState_test.cpp
+++ b/services/surfaceflinger/tests/LayerState_test.cpp
@@ -28,66 +28,6 @@
 
 namespace test {
 
-TEST(LayerStateTest, ParcellingDisplayCaptureArgs) {
-    DisplayCaptureArgs args;
-    args.pixelFormat = ui::PixelFormat::RGB_565;
-    args.sourceCrop = Rect(0, 0, 500, 200);
-    args.frameScaleX = 2;
-    args.frameScaleY = 4;
-    args.captureSecureLayers = true;
-    args.displayToken = sp<BBinder>::make();
-    args.width = 10;
-    args.height = 20;
-    args.grayscale = true;
-
-    Parcel p;
-    args.writeToParcel(&p);
-    p.setDataPosition(0);
-
-    DisplayCaptureArgs args2;
-    args2.readFromParcel(&p);
-
-    ASSERT_EQ(args.pixelFormat, args2.pixelFormat);
-    ASSERT_EQ(args.sourceCrop, args2.sourceCrop);
-    ASSERT_EQ(args.frameScaleX, args2.frameScaleX);
-    ASSERT_EQ(args.frameScaleY, args2.frameScaleY);
-    ASSERT_EQ(args.captureSecureLayers, args2.captureSecureLayers);
-    ASSERT_EQ(args.displayToken, args2.displayToken);
-    ASSERT_EQ(args.width, args2.width);
-    ASSERT_EQ(args.height, args2.height);
-    ASSERT_EQ(args.grayscale, args2.grayscale);
-}
-
-TEST(LayerStateTest, ParcellingLayerCaptureArgs) {
-    LayerCaptureArgs args;
-    args.pixelFormat = ui::PixelFormat::RGB_565;
-    args.sourceCrop = Rect(0, 0, 500, 200);
-    args.frameScaleX = 2;
-    args.frameScaleY = 4;
-    args.captureSecureLayers = true;
-    args.layerHandle = sp<BBinder>::make();
-    args.excludeHandles = {sp<BBinder>::make(), sp<BBinder>::make()};
-    args.childrenOnly = false;
-    args.grayscale = true;
-
-    Parcel p;
-    args.writeToParcel(&p);
-    p.setDataPosition(0);
-
-    LayerCaptureArgs args2;
-    args2.readFromParcel(&p);
-
-    ASSERT_EQ(args.pixelFormat, args2.pixelFormat);
-    ASSERT_EQ(args.sourceCrop, args2.sourceCrop);
-    ASSERT_EQ(args.frameScaleX, args2.frameScaleX);
-    ASSERT_EQ(args.frameScaleY, args2.frameScaleY);
-    ASSERT_EQ(args.captureSecureLayers, args2.captureSecureLayers);
-    ASSERT_EQ(args.layerHandle, args2.layerHandle);
-    ASSERT_EQ(args.excludeHandles, args2.excludeHandles);
-    ASSERT_EQ(args.childrenOnly, args2.childrenOnly);
-    ASSERT_EQ(args.grayscale, args2.grayscale);
-}
-
 TEST(LayerStateTest, ParcellingScreenCaptureResultsWithFence) {
     ScreenCaptureResults results;
     results.buffer = sp<GraphicBuffer>::make(100u, 200u, PIXEL_FORMAT_RGBA_8888, 1u, 0u);
diff --git a/services/surfaceflinger/tests/LayerTransactionTest.h b/services/surfaceflinger/tests/LayerTransactionTest.h
index c9af432..03f9005 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -23,7 +23,7 @@
 
 #include <cutils/properties.h>
 #include <gtest/gtest.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <private/gui/ComposerService.h>
@@ -106,6 +106,10 @@
         return colorLayer;
     }
 
+    sp<SurfaceControl> mirrorSurface(SurfaceControl* mirrorFromSurface) {
+        return mClient->mirrorSurface(mirrorFromSurface);
+    }
+
     ANativeWindow_Buffer getBufferQueueLayerBuffer(const sp<SurfaceControl>& layer) {
         // wait for previous transactions (such as setSize) to complete
         Transaction().apply(true);
diff --git a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
index f9b4bba..76bae41 100644
--- a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
@@ -18,6 +18,7 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <gui/AidlUtil.h>
 #include <gui/BufferItemConsumer.h>
 #include <private/android_filesystem_config.h>
 #include "TransactionTestHarnesses.h"
@@ -64,7 +65,7 @@
     // only layerB is in this range
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = parent->getHandle();
-    captureArgs.sourceCrop = {0, 0, 32, 32};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(32, 32);
     ScreenCapture::captureLayers(&screenshot, captureArgs);
     screenshot->expectColor(Rect(0, 0, 32, 32), Color::BLUE);
 }
diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp
index 0ea0824..6cc1c51 100644
--- a/services/surfaceflinger/tests/MirrorLayer_test.cpp
+++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp
@@ -19,6 +19,8 @@
 #pragma clang diagnostic ignored "-Wconversion"
 
 #include <android-base/properties.h>
+#include <common/FlagManager.h>
+#include <gui/AidlUtil.h>
 #include <private/android_filesystem_config.h>
 #include "LayerTransactionTest.h"
 #include "utils/TransactionUtils.h"
@@ -78,6 +80,10 @@
             .show(mirrorLayer)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
+
     {
         SCOPED_TRACE("Initial Mirror");
         auto shot = screenshot();
@@ -172,6 +178,9 @@
             .show(mirrorLayer)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
     {
         SCOPED_TRACE("Initial Mirror BufferQueueLayer");
         auto shot = screenshot();
@@ -263,6 +272,9 @@
             .setLayer(mirrorLayer, INT32_MAX - 1)
             .apply();
 
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 550, 550).apply();
+    }
     {
         SCOPED_TRACE("Offscreen Mirror");
         auto shot = screenshot();
@@ -313,8 +325,15 @@
         ASSERT_NE(mirrorLayer, nullptr);
     }
 
+    sp<SurfaceControl> mirrorParent =
+            createLayer("Grandchild layer", 50, 50, ISurfaceComposerClient::eFXSurfaceBufferState);
+
     // Show the mirror layer, but don't reparent to a layer on screen.
-    Transaction().show(mirrorLayer).apply();
+    Transaction().reparent(mirrorLayer, mirrorParent).show(mirrorLayer).apply();
+
+    if (FlagManager::getInstance().detached_mirror()) {
+        Transaction().setPosition(mirrorLayer, 50, 50).apply();
+    }
 
     {
         SCOPED_TRACE("Offscreen Mirror");
@@ -331,8 +350,8 @@
         SCOPED_TRACE("Capture Mirror");
         // Capture just the mirror layer and child.
         LayerCaptureArgs captureArgs;
-        captureArgs.layerHandle = mirrorLayer->getHandle();
-        captureArgs.sourceCrop = childBounds;
+        captureArgs.layerHandle = mirrorParent->getHandle();
+        captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(childBounds);
         std::unique_ptr<ScreenCapture> shot;
         ScreenCapture::captureLayers(&shot, captureArgs);
         shot->expectSize(childBounds.width(), childBounds.height());
diff --git a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
index 15ff696..56cf13d 100644
--- a/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
+++ b/services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp
@@ -18,9 +18,12 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "gui/SurfaceComposerClient.h"
+#include "ui/DisplayId.h"
 
 namespace android {
 
@@ -37,7 +40,8 @@
 
         const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
         ASSERT_FALSE(ids.empty());
-        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+        mMainDisplayId = ids.front();
+        mMainDisplay = SurfaceComposerClient::getPhysicalDisplayToken(mMainDisplayId);
         SurfaceComposerClient::getDisplayState(mMainDisplay, &mMainDisplayState);
         SurfaceComposerClient::getActiveDisplayMode(mMainDisplay, &mMainDisplayMode);
 
@@ -49,14 +53,15 @@
     }
 
     virtual void TearDown() {
-        SurfaceComposerClient::destroyDisplay(mVirtualDisplay);
+        EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(mVirtualDisplay));
         LayerTransactionTest::TearDown();
         mColorLayer = 0;
     }
 
     void createDisplay(const ui::Size& layerStackSize, ui::LayerStack layerStack) {
+        static const std::string kDisplayName("VirtualDisplay");
         mVirtualDisplay =
-                SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
+                SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/);
         asTransaction([&](Transaction& t) {
             t.setDisplaySurface(mVirtualDisplay, mProducer);
             t.setDisplayLayerStack(mVirtualDisplay, layerStack);
@@ -85,6 +90,7 @@
     ui::DisplayState mMainDisplayState;
     ui::DisplayMode mMainDisplayMode;
     sp<IBinder> mMainDisplay;
+    PhysicalDisplayId mMainDisplayId;
     sp<IBinder> mVirtualDisplay;
     sp<IGraphicBufferProducer> mProducer;
     sp<SurfaceControl> mColorLayer;
@@ -119,6 +125,9 @@
     createDisplay(mMainDisplayState.layerStackSpaceRect, ui::DEFAULT_LAYER_STACK);
     createColorLayer(ui::DEFAULT_LAYER_STACK);
 
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
     asTransaction([&](Transaction& t) { t.setPosition(mColorLayer, 10, 10); });
 
     // Verify color layer renders correctly on main display and it is mirrored on the
@@ -133,6 +142,37 @@
     sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
 }
 
+TEST_F(MultiDisplayLayerBoundsTest, RenderLayerWithPromisedFenceInMirroredVirtualDisplay) {
+    // Create a display and use a unique layerstack ID for mirrorDisplay() so
+    // the contents of the main display are mirrored on to the virtual display.
+
+    // A unique layerstack ID must be used because sharing the same layerFE
+    // with more than one display is unsupported. A unique layerstack ensures
+    // that a different layerFE is used between displays.
+    constexpr ui::LayerStack layerStack{77687666}; // ASCII for MDLB (MultiDisplayLayerBounds)
+    createDisplay(mMainDisplayState.layerStackSpaceRect, layerStack);
+    createColorLayer(ui::DEFAULT_LAYER_STACK);
+
+    sp<SurfaceControl> mirrorSc =
+            SurfaceComposerClient::getDefault()->mirrorDisplay(mMainDisplayId);
+
+    asTransaction([&](Transaction& t) {
+        t.setPosition(mColorLayer, 10, 10);
+        t.setLayerStack(mirrorSc, layerStack);
+    });
+
+    // Verify color layer renders correctly on main display and it is mirrored on the
+    // virtual display.
+    std::unique_ptr<ScreenCapture> sc;
+    ScreenCapture::captureScreen(&sc, mMainDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+
+    ScreenCapture::captureScreen(&sc, mVirtualDisplay);
+    sc->expectColor(Rect(10, 10, 40, 50), mExpectedColor);
+    sc->expectColor(Rect(0, 0, 9, 9), {0, 0, 0, 255});
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/OWNERS b/services/surfaceflinger/tests/OWNERS
index 56f2f1b..7857961 100644
--- a/services/surfaceflinger/tests/OWNERS
+++ b/services/surfaceflinger/tests/OWNERS
@@ -4,5 +4,5 @@
 per-file Layer* = set noparent
 per-file Layer* = pdwilliams@google.com, vishnun@google.com, melodymhsu@google.com
 
-per-file LayerHistoryTest.cpp = file:/services/surfaceflinger/OWNERS
+per-file LayerHistoryIntegrationTest.cpp = file:/services/surfaceflinger/OWNERS
 per-file LayerInfoTest.cpp = file:/services/surfaceflinger/OWNERS
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/ScreenCapture_test.cpp b/services/surfaceflinger/tests/ScreenCapture_test.cpp
index 18262f6..c62f493 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -20,6 +20,7 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <gui/AidlUtil.h>
 #include <private/android_filesystem_config.h>
 #include <ui/DisplayState.h>
 
@@ -65,7 +66,7 @@
                     .show(mFGSurfaceControl);
         });
 
-        mCaptureArgs.sourceCrop = mDisplayRect;
+        mCaptureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(mDisplayRect);
         mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
     }
 
@@ -112,7 +113,7 @@
             shot->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
         }
 
-        mCaptureArgs.captureSecureLayers = true;
+        mCaptureArgs.captureArgs.captureSecureLayers = true;
         // AID_SYSTEM is allowed to capture secure content.
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
         ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
@@ -164,7 +165,7 @@
 
     // Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
     // to receive them...we are expected to take care with the results.
-    mCaptureArgs.captureSecureLayers = true;
+    mCaptureArgs.captureArgs.captureSecureLayers = true;
     ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
     ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
     ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
@@ -198,8 +199,8 @@
             .apply();
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = childLayer->getHandle();
-    captureArgs.sourceCrop = size;
-    captureArgs.captureSecureLayers = false;
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(size);
+    captureArgs.captureArgs.captureSecureLayers = false;
     {
         SCOPED_TRACE("parent hidden");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -208,7 +209,7 @@
         sc.expectColor(size, Color::BLACK);
     }
 
-    captureArgs.captureSecureLayers = true;
+    captureArgs.captureArgs.captureSecureLayers = true;
     {
         SCOPED_TRACE("capture secure parent not visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -218,7 +219,7 @@
     }
 
     Transaction().show(parentLayer).apply();
-    captureArgs.captureSecureLayers = false;
+    captureArgs.captureArgs.captureSecureLayers = false;
     {
         SCOPED_TRACE("parent visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -227,7 +228,7 @@
         sc.expectColor(size, Color::BLACK);
     }
 
-    captureArgs.captureSecureLayers = true;
+    captureArgs.captureArgs.captureSecureLayers = true;
     {
         SCOPED_TRACE("capture secure parent visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -259,8 +260,8 @@
             .apply();
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = childLayer->getHandle();
-    captureArgs.sourceCrop = size;
-    captureArgs.captureSecureLayers = false;
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(size);
+    captureArgs.captureArgs.captureSecureLayers = false;
     {
         SCOPED_TRACE("parent hidden");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -269,7 +270,7 @@
         sc.expectColor(size, Color::BLACK);
     }
 
-    captureArgs.captureSecureLayers = true;
+    captureArgs.captureArgs.captureSecureLayers = true;
     {
         SCOPED_TRACE("capture secure parent not visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -279,7 +280,7 @@
     }
 
     Transaction().show(parentLayer).apply();
-    captureArgs.captureSecureLayers = false;
+    captureArgs.captureArgs.captureSecureLayers = false;
     {
         SCOPED_TRACE("parent visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -288,7 +289,7 @@
         sc.expectColor(size, Color::BLACK);
     }
 
-    captureArgs.captureSecureLayers = true;
+    captureArgs.captureArgs.captureSecureLayers = true;
     {
         SCOPED_TRACE("capture secure parent visible");
         ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
@@ -361,14 +362,14 @@
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = fgHandle;
     captureArgs.childrenOnly = true;
-    captureArgs.excludeHandles = {child2->getHandle()};
+    captureArgs.captureArgs.excludeHandles = {child2->getHandle()};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->checkPixel(10, 10, 0, 0, 0);
     mCapture->checkPixel(0, 0, 200, 200, 200);
 }
 
 TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) {
-    mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
+    mCaptureArgs.captureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
     ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
     mCapture->expectBGColor(0, 0);
     // Doesn't capture FG layer which is at 64, 64
@@ -401,7 +402,7 @@
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = fgHandle;
     captureArgs.childrenOnly = true;
-    captureArgs.excludeHandles = {child2->getHandle()};
+    captureArgs.captureArgs.excludeHandles = {child2->getHandle()};
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->checkPixel(10, 10, 0, 0, 0);
     mCapture->checkPixel(0, 0, 200, 200, 200);
@@ -418,7 +419,7 @@
     // Captures child
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = child->getHandle();
-    captureArgs.sourceCrop = {0, 0, 10, 20};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(10, 20);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->expectColor(Rect(0, 0, 9, 9), {200, 200, 200, 255});
     // Area outside of child's bounds is transparent.
@@ -481,7 +482,7 @@
 
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = child->getHandle();
-    captureArgs.sourceCrop = {0, 0, 10, 10};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(10, 10);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect(0, 0, 9, 9), Color::RED);
@@ -623,7 +624,7 @@
     // red area to the right of the blue area
     mCapture->expectColor(Rect(30, 0, 59, 59), Color::RED);
 
-    captureArgs.sourceCrop = {0, 0, 30, 30};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(30, 30);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     // Capturing the cropped screen, cropping out the shown red area, should leave only the blue
     // area visible.
@@ -658,8 +659,8 @@
     // red area to the right of the blue area
     mCapture->expectColor(Rect(30, 0, 59, 59), Color::RED);
 
-    captureArgs.frameScaleX = 0.5f;
-    captureArgs.frameScaleY = 0.5f;
+    captureArgs.captureArgs.frameScaleX = 0.5f;
+    captureArgs.captureArgs.frameScaleY = 0.5f;
     sleep(1);
 
     ScreenCapture::captureLayers(&mCapture, captureArgs);
@@ -689,8 +690,8 @@
 
     LayerCaptureArgs captureArgs;
     captureArgs.layerHandle = redLayer->getHandle();
-    captureArgs.frameScaleX = INT32_MAX / 60;
-    captureArgs.frameScaleY = INT32_MAX / 60;
+    captureArgs.captureArgs.frameScaleX = INT32_MAX / 60;
+    captureArgs.captureArgs.frameScaleY = INT32_MAX / 60;
 
     ScreenCaptureResults captureResults;
     ASSERT_EQ(BAD_VALUE, ScreenCapture::captureLayers(captureArgs, captureResults));
@@ -736,7 +737,7 @@
     mCapture->expectColor(Rect(30, 30, 60, 60), Color::RED);
 
     // Passing flag secure so the blue layer should be screenshot too.
-    args.captureSecureLayers = true;
+    args.captureArgs.captureSecureLayers = true;
     ScreenCapture::captureLayers(&mCapture, args);
     mCapture->expectColor(Rect(0, 0, 30, 30), Color::BLUE);
     mCapture->expectColor(Rect(30, 30, 60, 60), Color::RED);
@@ -780,7 +781,7 @@
     // Reading color data will expectedly result in crash, only check usage bit
     // b/309965549 Checking that the usage bit is protected does not work for
     // devices that do not support usage protected.
-    mCaptureArgs.allowProtected = true;
+    mCaptureArgs.captureArgs.allowProtected = true;
     ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
     // ASSERT_EQ(GRALLOC_USAGE_PROTECTED, GRALLOC_USAGE_PROTECTED &
     // captureResults.buffer->getUsage());
@@ -898,7 +899,7 @@
 
     // Make screenshot request with current uid set. No layers were created with the current
     // uid so screenshot will be black.
-    captureArgs.uid = fakeUid;
+    captureArgs.captureArgs.uid = fakeUid;
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->expectColor(Rect(0, 0, 32, 32), Color::TRANSPARENT);
     mCapture->expectBorder(Rect(0, 0, 32, 32), Color::TRANSPARENT);
@@ -935,7 +936,7 @@
     mCapture->expectBorder(Rect(128, 128, 160, 160), Color::TRANSPARENT);
 
     // Screenshot from the fakeUid caller with no uid requested allows everything to be screenshot.
-    captureArgs.uid = -1;
+    captureArgs.captureArgs.uid = -1;
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->expectColor(Rect(128, 128, 160, 160), Color::RED);
     mCapture->expectBorder(Rect(128, 128, 160, 160), {63, 63, 195, 255});
@@ -955,7 +956,7 @@
     ScreenCapture::captureLayers(&mCapture, captureArgs);
     mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
 
-    captureArgs.grayscale = true;
+    captureArgs.captureArgs.grayscale = true;
 
     const uint8_t tolerance = 1;
 
@@ -1039,6 +1040,29 @@
     ASSERT_TRUE(mCapture->capturedHdrLayers());
 }
 
+TEST_F(ScreenCaptureTest, captureOffscreenNullSnapshot) {
+    sp<SurfaceControl> layer;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
+                                                ISurfaceComposerClient::eFXSurfaceBufferState,
+                                                mBGSurfaceControl.get()));
+
+    // A mirrored layer will not have a snapshot. Testing an offscreen mirrored layer
+    // ensures that the screenshot path handles cases where snapshots are null.
+    sp<SurfaceControl> mirroredLayer;
+    ASSERT_NO_FATAL_FAILURE(mirroredLayer = mirrorSurface(layer.get()));
+
+    LayerCaptureArgs captureArgs;
+    captureArgs.layerHandle = mirroredLayer->getHandle();
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(1, 1);
+
+    // Screenshot path should only use the children of the layer hierarchy so
+    // that it will not create a new snapshot. A snapshot would otherwise be
+    // created to pass on the properties of the parent, which is not needed
+    // for the purposes of this test since we explicitly want a null snapshot.
+    captureArgs.childrenOnly = true;
+    ScreenCapture::captureLayers(&mCapture, captureArgs);
+}
+
 // In the following tests we verify successful skipping of a parent layer,
 // so we use the same verification logic and only change how we mutate
 // the parent layer to verify that various properties are ignored.
diff --git a/services/surfaceflinger/tests/Stress_test.cpp b/services/surfaceflinger/tests/Stress_test.cpp
deleted file mode 100644
index b30df5e..0000000
--- a/services/surfaceflinger/tests/Stress_test.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
-#include <gtest/gtest.h>
-
-#include <gui/SurfaceComposerClient.h>
-
-#include <utils/String8.h>
-
-#include <thread>
-#include <functional>
-#include <layerproto/LayerProtoParser.h>
-
-namespace android {
-
-TEST(SurfaceFlingerStress, create_and_destroy) {
-    auto do_stress = []() {
-        sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
-        ASSERT_EQ(NO_ERROR, client->initCheck());
-        for (int j = 0; j < 1000; j++) {
-            auto surf = client->createSurface(String8("t"), 100, 100,
-                    PIXEL_FORMAT_RGBA_8888, 0);
-            ASSERT_TRUE(surf != nullptr);
-            surf.clear();
-        }
-    };
-
-    std::vector<std::thread> threads;
-    for (int i = 0; i < 10; i++) {
-        threads.push_back(std::thread(do_stress));
-    }
-    for (auto& thread : threads) {
-        thread.join();
-    }
-}
-
-perfetto::protos::LayersProto generateLayerProto() {
-    perfetto::protos::LayersProto layersProto;
-    std::array<perfetto::protos::LayerProto*, 10> layers = {};
-    for (size_t i = 0; i < layers.size(); ++i) {
-        layers[i] = layersProto.add_layers();
-        layers[i]->set_id(i);
-    }
-
-    layers[0]->add_children(1);
-    layers[1]->set_parent(0);
-    layers[0]->add_children(2);
-    layers[2]->set_parent(0);
-    layers[0]->add_children(3);
-    layers[3]->set_parent(0);
-    layers[2]->add_children(4);
-    layers[4]->set_parent(2);
-    layers[3]->add_children(5);
-    layers[5]->set_parent(3);
-    layers[5]->add_children(6);
-    layers[6]->set_parent(5);
-    layers[5]->add_children(7);
-    layers[7]->set_parent(5);
-    layers[6]->add_children(8);
-    layers[8]->set_parent(6);
-
-    layers[4]->set_z_order_relative_of(3);
-    layers[3]->add_relatives(4);
-    layers[8]->set_z_order_relative_of(9);
-    layers[9]->add_relatives(8);
-    layers[3]->set_z_order_relative_of(1);
-    layers[1]->add_relatives(3);
-
-/* ----------------------------
- *       - 0 -      - 9 -
- *      /  |  \
- *     1   2   3(1)
- *         |    |
- *         4(3) 5
- *             / \
- *            6   7
- *            |
- *            8(9)
- * -------------------------- */
-
-    return layersProto;
-}
-
-TEST(LayerProtoStress, mem_info) {
-    std::string cmd = "dumpsys meminfo ";
-    cmd += std::to_string(getpid());
-    system(cmd.c_str());
-    for (int i = 0; i < 100000; i++) {
-        perfetto::protos::LayersProto layersProto = generateLayerProto();
-        auto layerTree = surfaceflinger::LayerProtoParser::generateLayerTree(layersProto);
-        surfaceflinger::LayerProtoParser::layerTreeToString(layerTree);
-    }
-    system(cmd.c_str());
-}
-
-}
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/TextureFiltering_test.cpp b/services/surfaceflinger/tests/TextureFiltering_test.cpp
index c5d118c..3f39cf6 100644
--- a/services/surfaceflinger/tests/TextureFiltering_test.cpp
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
+#include <android/gui/DisplayCaptureArgs.h>
 #include <android/gui/ISurfaceComposerClient.h>
 #include <gtest/gtest.h>
-#include <gui/DisplayCaptureArgs.h>
+#include <gui/AidlUtil.h>
 #include <ui/GraphicTypes.h>
 #include <ui/Rect.h>
 
@@ -84,7 +85,7 @@
 };
 
 TEST_F(TextureFilteringTest, NoFiltering) {
-    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(100, 100);
     captureArgs.layerHandle = mParent->getHandle();
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
@@ -93,7 +94,7 @@
 }
 
 TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
-    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(100, 100);
     captureArgs.layerHandle = mParent->getHandle();
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
@@ -105,7 +106,7 @@
 TEST_F(TextureFilteringTest, BufferCropIsFiltered) {
     Transaction().setBufferCrop(mLayer, Rect{25, 25, 75, 75}).apply();
 
-    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(100, 100);
     captureArgs.layerHandle = mParent->getHandle();
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
@@ -114,9 +115,9 @@
 
 // Expect filtering because the output source crop is stretched to the output buffer's size.
 TEST_F(TextureFilteringTest, OutputSourceCropIsFiltered) {
-    captureArgs.frameScaleX = 2;
-    captureArgs.frameScaleY = 2;
-    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    captureArgs.captureArgs.frameScaleX = 2;
+    captureArgs.captureArgs.frameScaleY = 2;
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(25, 25, 75, 75);
     captureArgs.layerHandle = mParent->getHandle();
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
@@ -127,9 +128,9 @@
 // buffer's size.
 TEST_F(TextureFilteringTest, LayerCropOutputSourceCropIsFiltered) {
     Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
-    captureArgs.frameScaleX = 2;
-    captureArgs.frameScaleY = 2;
-    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    captureArgs.captureArgs.frameScaleX = 2;
+    captureArgs.captureArgs.frameScaleY = 2;
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(25, 25, 75, 75);
     captureArgs.layerHandle = mParent->getHandle();
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
@@ -139,8 +140,8 @@
 // Expect filtering because the layer is scaled up.
 TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
     captureArgs.layerHandle = mLayer->getHandle();
-    captureArgs.frameScaleX = 2;
-    captureArgs.frameScaleY = 2;
+    captureArgs.captureArgs.frameScaleX = 2;
+    captureArgs.captureArgs.frameScaleY = 2;
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     expectFiltered({0, 0, 100, 200}, {100, 0, 200, 200});
@@ -149,7 +150,7 @@
 // Expect no filtering because the output buffer's size matches the source crop.
 TEST_F(TextureFilteringTest, LayerCaptureOutputSourceCropNoFiltering) {
     captureArgs.layerHandle = mLayer->getHandle();
-    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(25, 25, 75, 75);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
@@ -162,7 +163,7 @@
     Transaction().setCrop(mLayer, Rect{10, 10, 90, 90}).apply();
 
     captureArgs.layerHandle = mLayer->getHandle();
-    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(25, 25, 75, 75);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
@@ -172,7 +173,7 @@
 // Expect no filtering because the output source crop and output buffer are the same size.
 TEST_F(TextureFilteringTest, OutputSourceCropDisplayFrameMatchNoFiltering) {
     captureArgs.layerHandle = mLayer->getHandle();
-    captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(25, 25, 75, 75);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 25, 50}, Color::RED);
@@ -206,7 +207,7 @@
     Transaction().setPosition(mParent, 100, 100).apply();
 
     captureArgs.layerHandle = mParent->getHandle();
-    captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+    captureArgs.captureArgs.sourceCrop = gui::aidl_utils::toARect(100, 100);
     ScreenCapture::captureLayers(&mCapture, captureArgs);
 
     mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
diff --git a/services/surfaceflinger/tests/TransactionTestHarnesses.h b/services/surfaceflinger/tests/TransactionTestHarnesses.h
index 797a64c..67a5247 100644
--- a/services/surfaceflinger/tests/TransactionTestHarnesses.h
+++ b/services/surfaceflinger/tests/TransactionTestHarnesses.h
@@ -16,9 +16,12 @@
 #ifndef ANDROID_TRANSACTION_TEST_HARNESSES
 #define ANDROID_TRANSACTION_TEST_HARNESSES
 
+#include <com_android_graphics_libgui_flags.h>
+#include <common/FlagManager.h>
 #include <ui/DisplayState.h>
 
 #include "LayerTransactionTest.h"
+#include "ui/LayerStack.h"
 
 namespace android {
 
@@ -36,9 +39,10 @@
             case RenderPath::VIRTUAL_DISPLAY:
 
                 const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+                const PhysicalDisplayId displayId = ids.front();
                 const auto displayToken = ids.empty()
                         ? nullptr
-                        : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+                        : SurfaceComposerClient::getPhysicalDisplayToken(displayId);
 
                 ui::DisplayState displayState;
                 SurfaceComposerClient::getDisplayState(displayToken, &displayState);
@@ -48,6 +52,16 @@
                 const ui::Size& resolution = displayMode.resolution;
 
                 sp<IBinder> vDisplay;
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+                sp<BufferItemConsumer> itemConsumer = sp<BufferItemConsumer>::make(
+                        // Sample usage bits from screenrecord
+                        GRALLOC_USAGE_HW_VIDEO_ENCODER | GRALLOC_USAGE_SW_READ_OFTEN);
+                sp<BufferListener> listener = sp<BufferListener>::make(this);
+                itemConsumer->setFrameAvailableListener(listener);
+                itemConsumer->setName(String8("Virtual disp consumer"));
+                itemConsumer->setDefaultBufferSize(resolution.getWidth(), resolution.getHeight());
+#else
                 sp<IGraphicBufferProducer> producer;
                 sp<IGraphicBufferConsumer> consumer;
                 sp<BufferItemConsumer> itemConsumer;
@@ -62,15 +76,32 @@
                                                                     GRALLOC_USAGE_SW_READ_OFTEN);
                 sp<BufferListener> listener = sp<BufferListener>::make(this);
                 itemConsumer->setFrameAvailableListener(listener);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
 
-                vDisplay = SurfaceComposerClient::createDisplay(String8("VirtualDisplay"),
-                                                                false /*secure*/);
+                static const std::string kDisplayName("VirtualDisplay");
+                vDisplay = SurfaceComposerClient::createVirtualDisplay(kDisplayName,
+                                                                       false /*isSecure*/);
+
+                constexpr ui::LayerStack layerStack{
+                        848472}; // ASCII for TTH (TransactionTestHarnesses)
+                sp<SurfaceControl> mirrorSc =
+                        SurfaceComposerClient::getDefault()->mirrorDisplay(displayId);
 
                 SurfaceComposerClient::Transaction t;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+                t.setDisplaySurface(vDisplay,
+                                    itemConsumer->getSurface()->getIGraphicBufferProducer());
+#else
                 t.setDisplaySurface(vDisplay, producer);
-                t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
                 t.setDisplayProjection(vDisplay, displayState.orientation,
                                        Rect(displayState.layerStackSpaceRect), Rect(resolution));
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setDisplayLayerStack(vDisplay, layerStack);
+                    t.setLayerStack(mirrorSc, layerStack);
+                } else {
+                    t.setDisplayLayerStack(vDisplay, ui::DEFAULT_LAYER_STACK);
+                }
                 t.apply();
                 SurfaceComposerClient::Transaction().apply(true);
 
@@ -85,7 +116,16 @@
                 constexpr bool kContainsHdr = false;
                 auto sc = std::make_unique<ScreenCapture>(item.mGraphicBuffer, kContainsHdr);
                 itemConsumer->releaseBuffer(item);
-                SurfaceComposerClient::destroyDisplay(vDisplay);
+
+                // Possible race condition with destroying virtual displays, in which
+                // CompositionEngine::present may attempt to be called on the same
+                // display multiple times. The layerStack is set to invalid here so
+                // that the display is ignored if that scenario occurs.
+                if (FlagManager::getInstance().ce_fence_promise()) {
+                    t.setLayerStack(mirrorSc, ui::INVALID_LAYER_STACK);
+                    t.apply(true);
+                }
+                SurfaceComposerClient::destroyVirtualDisplay(vDisplay);
                 return sc;
         }
     }
diff --git a/services/surfaceflinger/tests/VirtualDisplay_test.cpp b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
index f31f582..d69378c 100644
--- a/services/surfaceflinger/tests/VirtualDisplay_test.cpp
+++ b/services/surfaceflinger/tests/VirtualDisplay_test.cpp
@@ -27,6 +27,12 @@
 class VirtualDisplayTest : public ::testing::Test {
 protected:
     void SetUp() override {
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
+        mGLConsumer = sp<GLConsumer>::make(GLConsumer::TEXTURE_EXTERNAL, true, false, false);
+        mGLConsumer->setName(String8("Virtual disp consumer"));
+        mGLConsumer->setDefaultBufferSize(100, 100);
+        mProducer = mGLConsumer->getSurface()->getIGraphicBufferProducer();
+#else
         sp<IGraphicBufferConsumer> consumer;
 
         BufferQueue::createBufferQueue(&mProducer, &consumer);
@@ -34,6 +40,7 @@
         consumer->setDefaultBufferSize(100, 100);
 
         mGLConsumer = sp<GLConsumer>::make(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false);
+#endif // COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ)
     }
 
     sp<IGraphicBufferProducer> mProducer;
@@ -41,14 +48,15 @@
 };
 
 TEST_F(VirtualDisplayTest, VirtualDisplayDestroyedSurfaceReuse) {
+    static const std::string kDisplayName("VirtualDisplay");
     sp<IBinder> virtualDisplay =
-            SurfaceComposerClient::createDisplay(String8("VirtualDisplay"), false /*secure*/);
+            SurfaceComposerClient::createVirtualDisplay(kDisplayName, false /*isSecure*/);
 
     SurfaceComposerClient::Transaction t;
     t.setDisplaySurface(virtualDisplay, mProducer);
     t.apply(true);
 
-    SurfaceComposerClient::destroyDisplay(virtualDisplay);
+    EXPECT_EQ(NO_ERROR, SurfaceComposerClient::destroyVirtualDisplay(virtualDisplay));
     virtualDisplay.clear();
     // Sync here to ensure the display was completely destroyed in SF
     t.apply(true);
diff --git a/services/surfaceflinger/tests/benchmarks/Android.bp b/services/surfaceflinger/tests/benchmarks/Android.bp
new file mode 100644
index 0000000..1c47be34
--- /dev/null
+++ b/services/surfaceflinger/tests/benchmarks/Android.bp
@@ -0,0 +1,31 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_benchmark {
+    name: "surfaceflinger_microbenchmarks",
+    srcs: [
+        ":libsurfaceflinger_mock_sources",
+        ":libsurfaceflinger_sources",
+        "*.cpp",
+    ],
+    defaults: [
+        "libsurfaceflinger_mocks_defaults",
+        "skia_renderengine_deps",
+        "surfaceflinger_defaults",
+    ],
+    static_libs: [
+        "libgmock",
+        "libgtest",
+        "libc++fs",
+    ],
+    header_libs: [
+        "libsurfaceflinger_mocks_headers",
+        "surfaceflinger_tests_common_headers",
+    ],
+}
diff --git a/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp b/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp
new file mode 100644
index 0000000..7641a45
--- /dev/null
+++ b/services/surfaceflinger/tests/benchmarks/LayerLifecycleManager_benchmarks.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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 <memory>
+#include <optional>
+
+#include <benchmark/benchmark.h>
+
+#include <Client.h> // temporarily needed for LayerCreationArgs
+#include <FrontEnd/LayerCreationArgs.h>
+#include <FrontEnd/LayerLifecycleManager.h>
+#include <LayerLifecycleManagerHelper.h>
+
+namespace android::surfaceflinger {
+
+namespace {
+
+using namespace android::surfaceflinger::frontend;
+
+static void addRemoveLayers(benchmark::State& state) {
+    LayerLifecycleManager lifecycleManager;
+    for (auto _ : state) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(1));
+        layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(2));
+        layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(3));
+        lifecycleManager.addLayers(std::move(layers));
+        lifecycleManager.onHandlesDestroyed({{1, "1"}, {2, "2"}, {3, "3"}});
+        lifecycleManager.commitChanges();
+    }
+}
+BENCHMARK(addRemoveLayers);
+
+static void updateClientStates(benchmark::State& state) {
+    LayerLifecycleManager lifecycleManager;
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+    lifecycleManager.commitChanges();
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    auto& transactionState = transactions.back().states.front();
+    transactionState.state.what = layer_state_t::eColorChanged;
+    transactionState.state.color.rgb = {0.f, 0.f, 0.f};
+    transactionState.layerId = 1;
+    lifecycleManager.applyTransactions(transactions);
+    lifecycleManager.commitChanges();
+    int i = 0;
+    for (auto s : state) {
+        if (i++ % 100 == 0) i = 0;
+        transactionState.state.color.b = static_cast<float>(i / 100.f);
+        lifecycleManager.applyTransactions(transactions);
+        lifecycleManager.commitChanges();
+    }
+}
+BENCHMARK(updateClientStates);
+
+static void updateClientStatesNoChanges(benchmark::State& state) {
+    LayerLifecycleManager lifecycleManager;
+    std::vector<std::unique_ptr<RequestedLayerState>> layers;
+    layers.emplace_back(LayerLifecycleManagerHelper::rootLayer(1));
+    lifecycleManager.addLayers(std::move(layers));
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    auto& transactionState = transactions.back().states.front();
+    transactionState.state.what = layer_state_t::eColorChanged;
+    transactionState.state.color.rgb = {0.f, 0.f, 0.f};
+    transactionState.layerId = 1;
+    lifecycleManager.applyTransactions(transactions);
+    lifecycleManager.commitChanges();
+    for (auto _ : state) {
+        lifecycleManager.applyTransactions(transactions);
+        lifecycleManager.commitChanges();
+    }
+}
+BENCHMARK(updateClientStatesNoChanges);
+
+} // namespace
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/tests/benchmarks/LocklessQueue_benchmarks.cpp b/services/surfaceflinger/tests/benchmarks/LocklessQueue_benchmarks.cpp
new file mode 100644
index 0000000..60bd58a
--- /dev/null
+++ b/services/surfaceflinger/tests/benchmarks/LocklessQueue_benchmarks.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 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 <memory>
+#include <optional>
+
+#include <benchmark/benchmark.h>
+
+#include <LocklessQueue.h>
+
+namespace android::surfaceflinger {
+
+namespace {
+static void pushPop(benchmark::State& state) {
+    LocklessQueue<std::vector<uint32_t>> queue;
+    for (auto _ : state) {
+        queue.push({10, 5});
+        std::vector<uint32_t> poppedValue = *queue.pop();
+        benchmark::DoNotOptimize(poppedValue);
+    }
+}
+BENCHMARK(pushPop);
+
+} // namespace
+} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/tests/benchmarks/main.cpp b/services/surfaceflinger/tests/benchmarks/main.cpp
new file mode 100644
index 0000000..685c7c6
--- /dev/null
+++ b/services/surfaceflinger/tests/benchmarks/main.cpp
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 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 <benchmark/benchmark.h>
+BENCHMARK_MAIN();
diff --git a/services/surfaceflinger/tests/common/Android.bp b/services/surfaceflinger/tests/common/Android.bp
new file mode 100644
index 0000000..2dfa8af
--- /dev/null
+++ b/services/surfaceflinger/tests/common/Android.bp
@@ -0,0 +1,13 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_headers {
+    name: "surfaceflinger_tests_common_headers",
+    export_include_dirs: ["."],
+}
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
new file mode 100644
index 0000000..ae380ad
--- /dev/null
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -0,0 +1,539 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <gui/fake/BufferData.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <ui/ShadowSettings.h>
+
+#include <Client.h> // temporarily needed for LayerCreationArgs
+#include <FrontEnd/LayerCreationArgs.h>
+#include <FrontEnd/LayerHierarchy.h>
+#include <FrontEnd/LayerLifecycleManager.h>
+#include <FrontEnd/LayerSnapshotBuilder.h>
+#include <Layer.h> // needed for framerate
+
+namespace android::surfaceflinger::frontend {
+
+class LayerLifecycleManagerHelper {
+public:
+    LayerLifecycleManagerHelper(LayerLifecycleManager& layerLifecycleManager)
+          : mLifecycleManager(layerLifecycleManager) {}
+    ~LayerLifecycleManagerHelper() = default;
+
+    static LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+                                        uint32_t layerIdToMirror) {
+        LayerCreationArgs args(std::make_optional(id));
+        args.name = "testlayer";
+        args.addToRoot = canBeRoot;
+        args.parentId = parentId;
+        args.layerIdToMirror = layerIdToMirror;
+        return args;
+    }
+
+    static LayerCreationArgs createDisplayMirrorArgs(uint32_t id,
+                                                     ui::LayerStack layerStackToMirror) {
+        LayerCreationArgs args(std::make_optional(id));
+        args.name = "testlayer";
+        args.addToRoot = true;
+        args.layerStackToMirror = layerStackToMirror;
+        return args;
+    }
+
+    static std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
+        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/true,
+                                                                /*parent=*/UNASSIGNED_LAYER_ID,
+                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
+    }
+
+    static std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
+        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
+                                                                parentId,
+                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
+    }
+
+    static std::vector<TransactionState> setZTransaction(uint32_t id, int32_t z) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.z = z;
+        return transactions;
+    }
+
+    void createRootLayer(uint32_t id) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+                           /*mirror=*/UNASSIGNED_LAYER_ID)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void createRootLayerWithUid(uint32_t id, gui::Uid uid) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        auto args = createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+                               /*mirror=*/UNASSIGNED_LAYER_ID);
+        args.ownerUid = uid.val();
+        layers.emplace_back(std::make_unique<RequestedLayerState>(args));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createDisplayMirrorArgs(/*id=*/id, layerStack)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void createLayer(uint32_t id, uint32_t parentId) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                           /*mirror=*/UNASSIGNED_LAYER_ID)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    std::vector<TransactionState> reparentLayerTransaction(uint32_t id, uint32_t newParentId) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().parentId = newParentId;
+        transactions.back().states.front().state.what = layer_state_t::eReparent;
+        transactions.back().states.front().relativeParentId = UNASSIGNED_LAYER_ID;
+        transactions.back().states.front().layerId = id;
+        return transactions;
+    }
+
+    void reparentLayer(uint32_t id, uint32_t newParentId) {
+        mLifecycleManager.applyTransactions(reparentLayerTransaction(id, newParentId));
+    }
+
+    std::vector<TransactionState> relativeLayerTransaction(uint32_t id, uint32_t relativeParentId) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().relativeParentId = relativeParentId;
+        transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+        transactions.back().states.front().layerId = id;
+        return transactions;
+    }
+
+    void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+        mLifecycleManager.applyTransactions(relativeLayerTransaction(id, relativeParentId));
+    }
+
+    void removeRelativeZ(uint32_t id) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setPosition(uint32_t id, float x, float y) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::ePositionChanged;
+        transactions.back().states.front().state.x = x;
+        transactions.back().states.front().state.y = y;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
+        std::vector<std::unique_ptr<RequestedLayerState>> layers;
+        layers.emplace_back(std::make_unique<RequestedLayerState>(
+                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+                           /*mirror=*/layerIdToMirror)));
+        mLifecycleManager.addLayers(std::move(layers));
+    }
+
+    void updateBackgroundColor(uint32_t id, half alpha) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+        transactions.back().states.front().state.bgColor.a = alpha;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
+
+    void setZ(uint32_t id, int32_t z) {
+        mLifecycleManager.applyTransactions(setZTransaction(id, z));
+    }
+
+    void setCrop(uint32_t id, const Rect& crop) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eCropChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.crop = crop;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
+        transactions.back().states.front().state.flags = flags;
+        transactions.back().states.front().state.mask = mask;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setAlpha(uint32_t id, float alpha) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.color.a = static_cast<half>(alpha);
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void hideLayer(uint32_t id) {
+        setFlags(id, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
+    }
+
+    void showLayer(uint32_t id) { setFlags(id, layer_state_t::eLayerHidden, 0); }
+
+    void setColor(uint32_t id, half3 rgb = half3(1._hf, 1._hf, 1._hf)) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::eColorChanged;
+        transactions.back().states.front().state.color.rgb = rgb;
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setLayerStack(uint32_t id, int32_t layerStack) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eLayerStackChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.layerStack = ui::LayerStack::fromValue(layerStack);
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setTouchableRegion(uint32_t id, Region region) {
+        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->token = sp<BBinder>::make();
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setInputInfo(uint32_t id, std::function<void(gui::WindowInfo&)> configureInput) {
+        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();
+        if (!inputInfo->token) {
+            inputInfo->token = sp<BBinder>::make();
+        }
+        configureInput(*inputInfo);
+
+        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);
+    }
+
+    void setFrameRateSelectionPriority(uint32_t id, int32_t priority) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateSelectionPriority;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionPriority = priority;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRate(uint32_t id, float frameRate, int8_t compatibility,
+                      int8_t changeFrameRateStrategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRate = frameRate;
+        transactions.back().states.front().state.frameRateCompatibility = compatibility;
+        transactions.back().states.front().state.changeFrameRateStrategy = changeFrameRateStrategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRate(uint32_t id, Layer::FrameRate framerate) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRate = framerate.vote.rate.getValue();
+        transactions.back().states.front().state.frameRateCompatibility = 0;
+        transactions.back().states.front().state.changeFrameRateStrategy = 0;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRateCategory(uint32_t id, int8_t frameRateCategory) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eFrameRateCategoryChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateCategory = frameRateCategory;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eFrameRateSelectionStrategyChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDefaultFrameRateCompatibility(uint32_t id, int8_t defaultFrameRateCompatibility) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what =
+                layer_state_t::eDefaultFrameRateCompatibilityChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.defaultFrameRateCompatibility =
+                defaultFrameRateCompatibility;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setRoundedCorners(uint32_t id, float radius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eCornerRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.cornerRadius = radius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setBuffer(uint32_t id, std::shared_ptr<renderengine::ExternalTexture> texture) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBufferChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().externalTexture = texture;
+        transactions.back().states.front().state.bufferData =
+                std::make_shared<fake::BufferData>(texture->getId(), texture->getWidth(),
+                                                   texture->getHeight(), texture->getPixelFormat(),
+                                                   texture->getUsage());
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setBuffer(uint32_t id) {
+        static uint64_t sBufferId = 1;
+        setBuffer(id,
+                  std::make_shared<renderengine::mock::
+                                           FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                                sBufferId++,
+                                                                HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
+    }
+
+    void setFrontBuffer(uint32_t id) {
+        static uint64_t sBufferId = 1;
+        setBuffer(id,
+                  std::make_shared<renderengine::mock::FakeExternalTexture>(
+                          1U /*width*/, 1U /*height*/, sBufferId++, HAL_PIXEL_FORMAT_RGBA_8888,
+                          GRALLOC_USAGE_PROTECTED | AHARDWAREBUFFER_USAGE_FRONT_BUFFER /*usage*/));
+    }
+
+    void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.bufferCrop = bufferCrop;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDamageRegion(uint32_t id, const Region& damageRegion) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDataspace(uint32_t id, ui::Dataspace dataspace) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eDataspaceChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.dataspace = dataspace;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
+        layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
+
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.matrix = matrix;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setShadowRadius(uint32_t id, float shadowRadius) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eShadowRadiusChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.shadowRadius = shadowRadius;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setTrustedOverlay(uint32_t id, gui::TrustedOverlay trustedOverlay) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.trustedOverlay = trustedOverlay;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setDropInputMode(uint32_t id, gui::DropInputMode dropInputMode) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().state.what = layer_state_t::eDropInputModeChanged;
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.dropInputMode = dropInputMode;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setGameMode(uint32_t id, gui::GameMode gameMode) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+        transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+        transactions.back().states.front().state.metadata = LayerMetadata();
+        transactions.back().states.front().state.metadata.setInt32(METADATA_GAME_MODE,
+                                                                   static_cast<int32_t>(gameMode));
+        transactions.back().states.front().layerId = id;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+    void setEdgeExtensionEffect(uint32_t id, int edge) {
+        std::vector<TransactionState> transactions;
+        transactions.emplace_back();
+        transactions.back().states.push_back({});
+
+        transactions.back().states.front().layerId = id;
+        transactions.back().states.front().state.what |= layer_state_t::eEdgeExtensionChanged;
+        transactions.back().states.front().state.edgeExtensionParameters =
+                gui::EdgeExtensionParameters();
+        transactions.back().states.front().state.edgeExtensionParameters.extendLeft = edge & LEFT;
+        transactions.back().states.front().state.edgeExtensionParameters.extendRight = edge & RIGHT;
+        transactions.back().states.front().state.edgeExtensionParameters.extendTop = edge & TOP;
+        transactions.back().states.front().state.edgeExtensionParameters.extendBottom =
+                edge & BOTTOM;
+        mLifecycleManager.applyTransactions(transactions);
+    }
+
+private:
+    LayerLifecycleManager& mLifecycleManager;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index f529f7c..f1bd87c 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -29,7 +29,7 @@
         "mock/DisplayHardware/MockComposer.cpp",
         "mock/DisplayHardware/MockHWC2.cpp",
         "mock/DisplayHardware/MockIPower.cpp",
-        "mock/DisplayHardware/MockIPowerHintSession.cpp",
+        "mock/DisplayHardware/MockPowerHintSessionWrapper.cpp",
         "mock/DisplayHardware/MockPowerAdvisor.cpp",
         "mock/MockEventThread.cpp",
         "mock/MockFrameTimeline.cpp",
@@ -58,6 +58,7 @@
     ],
     test_suites: ["device-tests"],
     static_libs: ["libc++fs"],
+    header_libs: ["surfaceflinger_tests_common_headers"],
     srcs: [
         ":libsurfaceflinger_mock_sources",
         ":libsurfaceflinger_sources",
@@ -66,32 +67,29 @@
         "BackgroundExecutorTest.cpp",
         "CommitTest.cpp",
         "CompositionTest.cpp",
+        "DaltonizerTest.cpp",
         "DisplayIdGeneratorTest.cpp",
         "DisplayTransactionTest.cpp",
         "DisplayDevice_GetBestColorModeTest.cpp",
-        "DisplayDevice_InitiateModeChange.cpp",
         "DisplayDevice_SetDisplayBrightnessTest.cpp",
         "DisplayDevice_SetProjectionTest.cpp",
+        "DisplayModeControllerTest.cpp",
         "EventThreadTest.cpp",
         "FlagManagerTest.cpp",
         "FpsReporterTest.cpp",
         "FpsTest.cpp",
         "FramebufferSurfaceTest.cpp",
         "FrameRateOverrideMappingsTest.cpp",
-        "FrameRateSelectionPriorityTest.cpp",
-        "FrameRateSelectionStrategyTest.cpp",
         "FrameTimelineTest.cpp",
-        "GameModeTest.cpp",
         "HWComposerTest.cpp",
+        "JankTrackerTest.cpp",
         "OneShotTimerTest.cpp",
-        "LayerHistoryTest.cpp",
         "LayerHistoryIntegrationTest.cpp",
         "LayerInfoTest.cpp",
         "LayerMetadataTest.cpp",
         "LayerHierarchyTest.cpp",
         "LayerLifecycleManagerTest.cpp",
         "LayerSnapshotTest.cpp",
-        "LayerTest.cpp",
         "LayerTestUtils.cpp",
         "MessageQueueTest.cpp",
         "PowerAdvisorTest.cpp",
@@ -114,9 +112,7 @@
         "SurfaceFlinger_SetDisplayStateTest.cpp",
         "SurfaceFlinger_SetPowerModeInternalTest.cpp",
         "SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
-        "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp",
         "SchedulerTest.cpp",
-        "SetFrameRateTest.cpp",
         "RefreshRateSelectorTest.cpp",
         "RefreshRateStatsTest.cpp",
         "RegionSamplingTest.cpp",
@@ -149,6 +145,8 @@
         "android.hardware.graphics.composer3-ndk_static",
         "android.hardware.power-ndk_static",
         "librenderengine_deps",
+        "libsurfaceflinger_common_test_deps",
+        "libsurfaceflinger_proto_deps",
     ],
     static_libs: [
         "android.hardware.common-V2-ndk",
@@ -167,19 +165,16 @@
         "libframetimeline",
         "libgmock",
         "libgui_mocks",
-        "liblayers_proto",
         "libperfetto_client_experimental",
         "librenderengine",
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
-        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
         "libtonemap",
         "perfetto_trace_protos",
-        "libsurfaceflingerflags_test",
     ],
     shared_libs: [
         "android.hardware.configstore-utils",
@@ -208,7 +203,7 @@
         "libsync",
         "libui",
         "libutils",
-        "server_configurable_flags",
+        "libtracing_perfetto",
     ],
     header_libs: [
         "android.hardware.graphics.composer3-command-buffer",
diff --git a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
index 34e4ba5..d4c801f 100644
--- a/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
+++ b/services/surfaceflinger/tests/unittests/CommitAndCompositeTest.h
@@ -60,7 +60,7 @@
                            .setNativeWindow(mNativeWindow)
                            .setPowerMode(hal::PowerMode::ON)
                            .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
-                           .skipRegisterDisplay()
+                           .skipSchedulerRegistration()
                            .inject();
     }
 
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
index df53d19..7f29418 100644
--- a/services/surfaceflinger/tests/unittests/CommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -17,9 +17,17 @@
 #undef LOG_TAG
 #define LOG_TAG "CommitTest"
 
+#include <DisplayHardware/HWComposer.h>
+#include <FrontEnd/LayerCreationArgs.h>
+#include <FrontEnd/RequestedLayerState.h>
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/Feature.h>
+#include <compositionengine/mock/CompositionEngine.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
-
+#include <gui/LayerMetadata.h>
+#include <gui/SurfaceComposerClient.h>
+#include <mock/DisplayHardware/MockComposer.h>
 #include <renderengine/mock/RenderEngine.h>
 #include "TestableSurfaceFlinger.h"
 
@@ -27,18 +35,27 @@
 
 class CommitTest : public testing::Test {
 protected:
-    CommitTest() {
+    TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    void flinger_setup() {
         mFlinger.setupMockScheduler();
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
         mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
     }
-    TestableSurfaceFlinger mFlinger;
-    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+
+    LayerCreationArgs createArgs(uint32_t id, LayerMetadata metadata, uint32_t parentId) {
+        LayerCreationArgs args(mFlinger.flinger(), nullptr, "layer",
+                               gui::ISurfaceComposerClient::eNoColorFill, metadata, id);
+        args.parentId = parentId;
+        return args;
+    }
 };
 
 namespace {
 
 TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+    flinger_setup();
     bool unused;
     bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
                                                        /*transactionsFlushed=*/0, unused);
@@ -47,6 +64,7 @@
 
 // Ensure that we handle eTransactionNeeded correctly
 TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+    flinger_setup();
     // update display level color matrix
     mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
     bool unused;
@@ -55,5 +73,92 @@
     EXPECT_TRUE(mustComposite);
 }
 
+TEST_F(CommitTest, metadataNotIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with unset flag
+    compositionengine::FeatureFlags flags;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), false);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata should not
+    // be included in the child layer when the flag is not set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_NE(parentMetadata, childMetadata);
+}
+
+TEST_F(CommitTest, metadataIsIncluded) {
+    mFlinger.setupMockScheduler();
+    mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+    compositionengine::mock::CompositionEngine* mCompositionEngine =
+            new compositionengine::mock::CompositionEngine();
+
+    // CompositionEngine setup with set flag
+    compositionengine::FeatureFlags flags;
+    flags |= compositionengine::Feature::kSnapshotLayerMetadata;
+    impl::HWComposer hwc = impl::HWComposer(std::make_unique<Hwc2::mock::Composer>());
+
+    EXPECT_CALL(*mCompositionEngine, getFeatureFlags).WillOnce(testing::Return(flags));
+    EXPECT_THAT(flags.test(compositionengine::Feature::kSnapshotLayerMetadata), true);
+
+    EXPECT_CALL(*mCompositionEngine, getHwComposer).WillOnce(testing::ReturnRef(hwc));
+
+    mFlinger.setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine>(mCompositionEngine));
+
+    // Create a parent layer with metadata and a child layer without. Metadata from the
+    // parent should be included in the child layer when the flag is set.
+    std::unordered_map<uint32_t, std::vector<uint8_t>> metadata = {{1, {'a', 'b'}}};
+    auto parentArgs = createArgs(1, LayerMetadata(metadata), UNASSIGNED_LAYER_ID);
+    auto parent = std::make_unique<frontend::RequestedLayerState>(parentArgs);
+    mFlinger.addLayer(parent);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(parentArgs));
+
+    auto childArgs = createArgs(11, LayerMetadata(), 1);
+    auto child = std::make_unique<frontend::RequestedLayerState>(childArgs);
+    mFlinger.addLayer(child);
+    mFlinger.injectLegacyLayer(sp<Layer>::make(childArgs));
+
+    bool unused;
+    bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                       /*transactionsFlushed=*/1, unused);
+    EXPECT_TRUE(mustComposite);
+
+    auto parentMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->layerMetadata.mMap;
+    auto childMetadata = mFlinger.mutableLayerSnapshotBuilder().getSnapshot(11)->layerMetadata.mMap;
+
+    EXPECT_EQ(metadata.at(1), parentMetadata.at(1));
+    EXPECT_EQ(parentMetadata, childMetadata);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 7d8a30a..23d3c16 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -15,7 +15,6 @@
  */
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#include "renderengine/ExternalTexture.h"
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 #pragma clang diagnostic ignored "-Wextra"
@@ -31,6 +30,7 @@
 #include <gui/IProducerListener.h>
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
+#include <renderengine/ExternalTexture.h>
 #include <renderengine/mock/FakeExternalTexture.h>
 #include <renderengine/mock/RenderEngine.h>
 #include <system/window.h>
@@ -70,6 +70,7 @@
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
 using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using namespace ftl::flag_operators;
 
 constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
 constexpr hal::HWLayerId HWC_LAYER = 5000;
@@ -148,7 +149,6 @@
     sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
             sp<compositionengine::mock::DisplaySurface>::make();
     sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
-    std::vector<sp<Layer>> mAuxiliaryLayers;
 
     sp<GraphicBuffer> mBuffer =
             sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
@@ -193,19 +193,19 @@
 template <typename LayerCase>
 void CompositionTest::captureScreenComposition() {
     LayerCase::setupForScreenCapture(this);
+    mFlinger.commit();
 
     const Rect sourceCrop(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
     constexpr bool regionSampling = false;
 
-    auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
-                                                ui::Dataspace::V0_SRGB, true, true);
+    auto renderArea =
+            DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
+                                      ui::Dataspace::V0_SRGB,
+                                      RenderArea::Options::CAPTURE_SECURE_LAYERS |
+                                              RenderArea::Options::HINT_FOR_SEAMLESS_TRANSITION);
 
-    auto traverseLayers = [this](const LayerVector::Visitor& visitor) {
-        return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(),
-                                                   CaptureArgs::UNSET_UID, {}, visitor);
-    };
-
-    auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+    auto getLayerSnapshotsFn = mFlinger.getLayerSnapshotsForScreenshotsFn(mDisplay->getLayerStack(),
+                                                                          CaptureArgs::UNSET_UID);
 
     const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
             GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
@@ -215,7 +215,7 @@
                                                                       HAL_PIXEL_FORMAT_RGBA_8888, 1,
                                                                       usage);
 
-    auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
+    auto future = mFlinger.renderScreenImpl(mDisplay, std::move(renderArea), getLayerSnapshotsFn,
                                             mCaptureScreenBuffer, regionSampling);
     ASSERT_TRUE(future.valid());
     const auto fenceResult = future.get();
@@ -282,7 +282,7 @@
                         .setSecure(Derived::IS_SECURE)
                         .setPowerMode(Derived::INIT_POWER_MODE)
                         .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector())
-                        .skipRegisterDisplay()
+                        .skipSchedulerRegistration()
                         .inject();
         Mock::VerifyAndClear(test->mNativeWindow.get());
 
@@ -457,7 +457,7 @@
     static constexpr IComposerClient::BlendMode BLENDMODE =
             IComposerClient::BlendMode::PREMULTIPLIED;
 
-    static void setupLatchedBuffer(CompositionTest* test, sp<Layer> layer) {
+    static void setupLatchedBuffer(CompositionTest* test, frontend::RequestedLayerState& layer) {
         Mock::VerifyAndClear(test->mRenderEngine);
 
         const auto buffer = std::make_shared<
@@ -467,21 +467,15 @@
                                                          LayerProperties::FORMAT,
                                                          LayerProperties::USAGE |
                                                                  GraphicBuffer::USAGE_HW_TEXTURE);
-
-        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
-        layerDrawingState.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
-        layerDrawingState.buffer = buffer;
-        layerDrawingState.acquireFence = Fence::NO_FENCE;
-        layerDrawingState.dataspace = ui::Dataspace::UNKNOWN;
-        layer->setSurfaceDamageRegion(
-                Region(Rect(LayerProperties::HEIGHT, LayerProperties::WIDTH)));
-
-        bool ignoredRecomputeVisibleRegions;
-        layer->latchBuffer(ignoredRecomputeVisibleRegions, 0);
+        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.externalTexture = buffer;
+        layer.bufferData->acquireFence = Fence::NO_FENCE;
+        layer.dataspace = ui::Dataspace::UNKNOWN;
+        layer.surfaceDamageRegion = Region(Rect(LayerProperties::HEIGHT, LayerProperties::WIDTH));
         Mock::VerifyAndClear(test->mRenderEngine);
     }
 
-    static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
+    static void setupLayerState(CompositionTest* test, frontend::RequestedLayerState& layer) {
         setupLatchedBuffer(test, layer);
     }
 
@@ -665,14 +659,12 @@
     using Base = BaseLayerProperties<SidebandLayerProperties>;
     static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::NONE;
 
-    static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
+    static void setupLayerState(CompositionTest* test, frontend::RequestedLayerState& layer) {
         sp<NativeHandle> stream =
                 NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                      false);
-        test->mFlinger.setLayerSidebandStream(layer, stream);
-        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
-        layerDrawingState.crop =
-                Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
+        layer.sidebandStream = stream;
+        layer.crop = Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
     }
 
     static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -750,17 +742,17 @@
 struct CursorLayerProperties : public BaseLayerProperties<CursorLayerProperties> {
     using Base = BaseLayerProperties<CursorLayerProperties>;
 
-    static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
+    static void setupLayerState(CompositionTest* test, frontend::RequestedLayerState& layer) {
         Base::setupLayerState(test, layer);
-        test->mFlinger.setLayerPotentialCursor(layer, true);
+        layer.potentialCursor = true;
     }
 };
 
 struct NoLayerVariant {
-    using FlingerLayerType = sp<Layer>;
-
-    static FlingerLayerType createLayer(CompositionTest*) { return FlingerLayerType(); }
-    static void injectLayer(CompositionTest*, FlingerLayerType) {}
+    static frontend::RequestedLayerState createLayer(CompositionTest*) {
+        return {LayerCreationArgs()};
+    }
+    static void injectLayer(CompositionTest*, frontend::RequestedLayerState&) {}
     static void cleanupInjectedLayers(CompositionTest*) {}
 
     static void setupCallExpectationsForDirtyGeometry(CompositionTest*) {}
@@ -770,10 +762,10 @@
 template <typename LayerProperties>
 struct BaseLayerVariant {
     template <typename L, typename F>
-    static sp<L> createLayerWithFactory(CompositionTest* test, F factory) {
+    static frontend::RequestedLayerState createLayerWithFactory(CompositionTest* test, F factory) {
         EXPECT_CALL(*test->mFlinger.scheduler(), postMessage(_)).Times(0);
 
-        sp<L> layer = factory();
+        auto layer = factory();
 
         // Layer should be registered with scheduler.
         EXPECT_EQ(1u, test->mFlinger.scheduler()->layerHistorySize());
@@ -787,27 +779,26 @@
         return layer;
     }
 
-    template <typename L>
-    static void initLayerDrawingStateAndComputeBounds(CompositionTest* test, sp<L> layer) {
-        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
-        layerDrawingState.layerStack = LAYER_STACK;
-        layerDrawingState.color = half4(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
-                                        LayerProperties::COLOR[2], LayerProperties::COLOR[3]);
-        layer->computeBounds(FloatRect(0, 0, 100, 100), ui::Transform(), 0.f /* shadowRadius */);
+    static void initLayerDrawingStateAndComputeBounds(CompositionTest* test,
+                                                      frontend::RequestedLayerState& layer) {
+        layer.layerStack = LAYER_STACK;
+        layer.color = half4(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
+                            LayerProperties::COLOR[2], LayerProperties::COLOR[3]);
     }
 
-    static void injectLayer(CompositionTest* test, sp<Layer> layer) {
+    static void injectLayer(CompositionTest* test, frontend::RequestedLayerState& layer) {
         EXPECT_CALL(*test->mComposer, createLayer(HWC_DISPLAY, _))
                 .WillOnce(DoAll(SetArgPointee<1>(HWC_LAYER), Return(Error::NONE)));
-
+        auto legacyLayer = test->mFlinger.getLegacyLayer(layer.id);
         auto outputLayer = test->mDisplay->getCompositionDisplay()->injectOutputLayerForTest(
-                layer->getCompositionEngineLayerFE());
+                legacyLayer->getCompositionEngineLayerFE({.id = layer.id}));
         outputLayer->editState().visibleRegion = Region(Rect(0, 0, 100, 100));
         outputLayer->editState().outputSpaceVisibleRegion = Region(Rect(0, 0, 100, 100));
 
         Mock::VerifyAndClear(test->mComposer);
 
-        test->mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
+        auto layerCopy = std::make_unique<frontend::RequestedLayerState>(layer);
+        test->mFlinger.addLayer(layerCopy);
         test->mFlinger.mutableVisibleRegionsDirty() = true;
     }
 
@@ -815,10 +806,9 @@
         EXPECT_CALL(*test->mComposer, destroyLayer(HWC_DISPLAY, HWC_LAYER))
                 .WillOnce(Return(Error::NONE));
 
+        test->mFlinger.destroyAllLayerHandles();
         test->mDisplay->getCompositionDisplay()->clearOutputLayers();
-        test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
         test->mFlinger.mutablePreviouslyComposedLayers().clear();
-
         // Layer should be unregistered with scheduler.
         test->mFlinger.commit();
         EXPECT_EQ(0u, test->mFlinger.scheduler()->layerHistorySize());
@@ -828,17 +818,17 @@
 template <typename LayerProperties>
 struct EffectLayerVariant : public BaseLayerVariant<LayerProperties> {
     using Base = BaseLayerVariant<LayerProperties>;
-    using FlingerLayerType = sp<Layer>;
-
-    static FlingerLayerType createLayer(CompositionTest* test) {
-        FlingerLayerType layer = Base::template createLayerWithFactory<Layer>(test, [test]() {
-            return sp<Layer>::make(LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(),
-                                                     "test-layer", LayerProperties::LAYER_FLAGS,
-                                                     LayerMetadata()));
+    static frontend::RequestedLayerState createLayer(CompositionTest* test) {
+        frontend::RequestedLayerState layer = Base::template createLayerWithFactory<
+                frontend::RequestedLayerState>(test, [test]() {
+            auto args = LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(), "test-layer",
+                                          LayerProperties::LAYER_FLAGS, LayerMetadata());
+            auto legacyLayer = sp<Layer>::make(args);
+            test->mFlinger.injectLegacyLayer(legacyLayer);
+            return frontend::RequestedLayerState(args);
         });
 
-        auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
-        layerDrawingState.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
         return layer;
     }
 
@@ -864,13 +854,15 @@
 template <typename LayerProperties>
 struct BufferLayerVariant : public BaseLayerVariant<LayerProperties> {
     using Base = BaseLayerVariant<LayerProperties>;
-    using FlingerLayerType = sp<Layer>;
 
-    static FlingerLayerType createLayer(CompositionTest* test) {
-        FlingerLayerType layer = Base::template createLayerWithFactory<Layer>(test, [test]() {
+    static frontend::RequestedLayerState createLayer(CompositionTest* test) {
+        frontend::RequestedLayerState layer = Base::template createLayerWithFactory<
+                frontend::RequestedLayerState>(test, [test]() {
             LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
                                    LayerProperties::LAYER_FLAGS, LayerMetadata());
-            return sp<Layer>::make(args);
+            auto legacyLayer = sp<Layer>::make(args);
+            test->mFlinger.injectLegacyLayer(legacyLayer);
+            return frontend::RequestedLayerState(args);
         });
 
         LayerProperties::setupLayerState(test, layer);
@@ -912,13 +904,14 @@
 template <typename LayerProperties>
 struct ContainerLayerVariant : public BaseLayerVariant<LayerProperties> {
     using Base = BaseLayerVariant<LayerProperties>;
-    using FlingerLayerType = sp<Layer>;
 
-    static FlingerLayerType createLayer(CompositionTest* test) {
+    static frontend::RequestedLayerState createLayer(CompositionTest* test) {
         LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-container-layer",
                                LayerProperties::LAYER_FLAGS, LayerMetadata());
-        FlingerLayerType layer = sp<Layer>::make(args);
-        Base::template initLayerDrawingStateAndComputeBounds(test, layer);
+        sp<Layer> legacyLayer = sp<Layer>::make(args);
+        test->mFlinger.injectLegacyLayer(legacyLayer);
+        frontend::RequestedLayerState layer(args);
+        Base::initLayerDrawingStateAndComputeBounds(test, layer);
         return layer;
     }
 };
@@ -926,29 +919,19 @@
 template <typename LayerVariant, typename ParentLayerVariant>
 struct ChildLayerVariant : public LayerVariant {
     using Base = LayerVariant;
-    using FlingerLayerType = typename LayerVariant::FlingerLayerType;
     using ParentBase = ParentLayerVariant;
 
-    static FlingerLayerType createLayer(CompositionTest* test) {
+    static frontend::RequestedLayerState createLayer(CompositionTest* test) {
         // Need to create child layer first. Otherwise layer history size will be 2.
-        FlingerLayerType layer = Base::createLayer(test);
-
-        typename ParentBase::FlingerLayerType parentLayer = ParentBase::createLayer(test);
-        parentLayer->addChild(layer);
-        test->mFlinger.setLayerDrawingParent(layer, parentLayer);
-
-        test->mAuxiliaryLayers.push_back(parentLayer);
-
+        frontend::RequestedLayerState layer = Base::createLayer(test);
+        frontend::RequestedLayerState parentLayer = ParentBase::createLayer(test);
+        layer.parentId = parentLayer.id;
+        auto layerCopy = std::make_unique<frontend::RequestedLayerState>(parentLayer);
+        test->mFlinger.addLayer(layerCopy);
         return layer;
     }
 
-    static void cleanupInjectedLayers(CompositionTest* test) {
-        // Clear auxiliary layers first so that child layer can be successfully destroyed in the
-        // following call.
-        test->mAuxiliaryLayers.clear();
-
-        Base::cleanupInjectedLayers(test);
-    }
+    static void cleanupInjectedLayers(CompositionTest* test) { Base::cleanupInjectedLayers(test); }
 };
 
 /* ------------------------------------------------------------------------
@@ -1011,7 +994,7 @@
  */
 
 struct CompositionResultBaseVariant {
-    static void setupLayerState(CompositionTest*, sp<Layer>) {}
+    static void setupLayerState(CompositionTest*, frontend::RequestedLayerState&) {}
 
     template <typename Case>
     static void setupCallExpectationsForDirtyGeometry(CompositionTest* test) {
@@ -1051,9 +1034,8 @@
 };
 
 struct ForcedClientCompositionResultVariant : public CompositionResultBaseVariant {
-    static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
-        const auto outputLayer =
-                TestableSurfaceFlinger::findOutputLayerForDisplay(layer, test->mDisplay);
+    static void setupLayerState(CompositionTest* test, frontend::RequestedLayerState& layer) {
+        const auto outputLayer = test->mFlinger.findOutputLayerForDisplay(layer.id, test->mDisplay);
         LOG_FATAL_IF(!outputLayer);
         outputLayer->editState().forceClientComposition = true;
     }
@@ -1074,7 +1056,7 @@
 };
 
 struct ForcedClientCompositionViaDebugOptionResultVariant : public CompositionResultBaseVariant {
-    static void setupLayerState(CompositionTest* test, sp<Layer>) {
+    static void setupLayerState(CompositionTest* test, frontend::RequestedLayerState&) {
         test->mFlinger.mutableDebugDisableHWC() = true;
     }
 
@@ -1094,7 +1076,7 @@
 };
 
 struct EmptyScreenshotResultVariant {
-    static void setupLayerState(CompositionTest*, sp<Layer>) {}
+    static void setupLayerState(CompositionTest*, frontend::RequestedLayerState&) {}
 
     template <typename Case>
     static void setupCallExpectations(CompositionTest*) {}
@@ -1360,28 +1342,6 @@
  *  Layers with a parent layer with ISurfaceComposerClient::eSecure, on a non-secure display
  */
 
-TEST_F(CompositionTest,
-       HWCComposedBufferLayerWithSecureParentLayerOnInsecureDisplayWithDirtyGeometry) {
-    displayRefreshCompositionDirtyGeometry<CompositionCase<
-            InsecureDisplaySetupVariant,
-            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
-                              ContainerLayerVariant<SecureLayerProperties>>,
-            KeepCompositionTypeVariant<
-                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
-            ForcedClientCompositionResultVariant>>();
-}
-
-TEST_F(CompositionTest,
-       HWCComposedBufferLayerWithSecureParentLayerOnInsecureDisplayWithDirtyFrame) {
-    displayRefreshCompositionDirtyFrame<CompositionCase<
-            InsecureDisplaySetupVariant,
-            ChildLayerVariant<BufferLayerVariant<ParentSecureLayerProperties>,
-                              ContainerLayerVariant<SecureLayerProperties>>,
-            KeepCompositionTypeVariant<
-                    aidl::android::hardware::graphics::composer3::Composition::CLIENT>,
-            ForcedClientCompositionResultVariant>>();
-}
-
 TEST_F(CompositionTest, captureScreenBufferLayerWithSecureParentLayerOnInsecureDisplay) {
     captureScreenComposition<
             CompositionCase<InsecureDisplaySetupVariant,
diff --git a/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp
new file mode 100644
index 0000000..9f632a1
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DaltonizerTest.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+#include <math/mat4.h>
+#include <cmath>
+#include "Effects/Daltonizer.h"
+
+namespace android {
+
+class DaltonizerTest {
+private:
+    Daltonizer& mDaltonizer;
+
+public:
+    DaltonizerTest(Daltonizer& daltonizer) : mDaltonizer(daltonizer) {}
+
+    bool isDirty() const { return mDaltonizer.mDirty; }
+
+    float getLevel() const { return mDaltonizer.mLevel; }
+
+    ColorBlindnessType getType() const { return mDaltonizer.mType; }
+};
+
+constexpr float TOLERANCE = 0.01f;
+
+static bool isIdentityMatrix(mat4& matrix) {
+    for (size_t i = 0; i < 4; ++i) {
+        for (size_t j = 0; j < 4; ++j) {
+            if (i == j) {
+                // Check diagonal elements
+                if (std::fabs(matrix[i][j] - 1.0f) > TOLERANCE) {
+                    return false;
+                }
+            } else {
+                // Check off-diagonal elements
+                if (std::fabs(matrix[i][j]) > TOLERANCE) {
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+// Test Suite Name : DaltonizerTest, Test name: ConstructionDefaultValues
+TEST(DaltonizerTest, ConstructionDefaultValues) {
+    Daltonizer daltonizer;
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_TRUE(test.isDirty());
+    EXPECT_EQ(test.getType(), ColorBlindnessType::None);
+    mat4 matrix = daltonizer();
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, NotDirtyAfterColorMatrixReturned) {
+    Daltonizer daltonizer;
+
+    mat4 matrix = daltonizer();
+    DaltonizerTest test(daltonizer);
+
+    ASSERT_FALSE(test.isDirty());
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, LevelOutOfRangeTooLowIgnored) {
+    Daltonizer daltonizer;
+    // Get matrix to reset isDirty == false.
+    mat4 matrix = daltonizer();
+
+    daltonizer.setLevel(-1);
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_FALSE(test.isDirty());
+}
+
+TEST(DaltonizerTest, LevelOutOfRangeTooHighIgnored) {
+    Daltonizer daltonizer;
+    // Get matrix to reset isDirty == false.
+    mat4 matrix = daltonizer();
+
+    daltonizer.setLevel(11);
+    DaltonizerTest test(daltonizer);
+
+    EXPECT_EQ(test.getLevel(), 0.7f);
+    ASSERT_FALSE(test.isDirty());
+}
+
+TEST(DaltonizerTest, ColorCorrectionMatrixNonIdentical) {
+    Daltonizer daltonizer;
+    daltonizer.setType(ColorBlindnessType::Protanomaly);
+    daltonizer.setMode(ColorBlindnessMode::Correction);
+
+    mat4 matrix = daltonizer();
+
+    ASSERT_FALSE(isIdentityMatrix(matrix));
+}
+
+TEST(DaltonizerTest, LevelZeroColorMatrixEqIdentityMatrix) {
+    Daltonizer daltonizer;
+    daltonizer.setType(ColorBlindnessType::Protanomaly);
+    daltonizer.setMode(ColorBlindnessMode::Correction);
+    daltonizer.setLevel(0);
+
+    mat4 matrix = daltonizer();
+
+    ASSERT_TRUE(isIdentityMatrix(matrix));
+}
+
+} /* namespace android */
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
deleted file mode 100644
index c463a92..0000000
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include "DisplayTransactionTestHelpers.h"
-#include "mock/MockFrameRateMode.h"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt)                               \
-    ASSERT_TRUE(requestOpt);                                                            \
-    EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
-    EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
-
-namespace android {
-namespace {
-
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
-using DisplayModeRequest = display::DisplayModeRequest;
-
-class InitiateModeChangeTest : public DisplayTransactionTest {
-public:
-    using Action = DisplayDevice::DesiredModeAction;
-    void SetUp() override {
-        injectFakeBufferQueueFactory();
-        injectFakeNativeWindowSurfaceFactory();
-
-        PrimaryDisplayVariant::setupHwcHotplugCallExpectations(this);
-        PrimaryDisplayVariant::setupFramebufferConsumerBufferQueueCallExpectations(this);
-        PrimaryDisplayVariant::setupFramebufferProducerBufferQueueCallExpectations(this);
-        PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
-        PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
-
-        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                           DisplayHotplugEvent::CONNECTED);
-        mFlinger.configureAndCommit();
-
-        mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
-                           .setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60)
-                           .inject();
-    }
-
-protected:
-    sp<DisplayDevice> mDisplay;
-
-    static constexpr DisplayModeId kModeId60{0};
-    static constexpr DisplayModeId kModeId90{1};
-    static constexpr DisplayModeId kModeId120{2};
-
-    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
-            ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
-    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
-            ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
-    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
-            ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
-
-    static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
-    static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
-    static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
-    static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
-};
-
-TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) {
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode60)));
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, setDesiredMode) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, clearDesiredMode) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_TRUE(mDisplay->getDesiredMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateModeChange) REQUIRES(kMainThreadContext) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    const hal::VsyncPeriodChangeConstraints constraints{
-            .desiredTimeNanos = systemTime(),
-            .seamlessRequired = false,
-    };
-    hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateRenderRateSwitch) {
-    EXPECT_EQ(Action::InitiateRenderRateSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode30)));
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-TEST_F(InitiateModeChangeTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
-    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
-              mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
-
-    const hal::VsyncPeriodChangeConstraints constraints{
-            .desiredTimeNanos = systemTime(),
-            .seamlessRequired = false,
-    };
-    hal::VsyncPeriodChangeTimeline timeline;
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
-
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
-
-    EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
-    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getPendingMode());
-
-    mDisplay->clearDesiredMode();
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
new file mode 100644
index 0000000..d971150
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/DisplayModeControllerTest.cpp
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "Display/DisplayModeController.h"
+#include "Display/DisplaySnapshot.h"
+#include "DisplayHardware/HWComposer.h"
+#include "DisplayIdentificationTestHelpers.h"
+#include "FpsOps.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockFrameRateMode.h"
+
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt)                               \
+    ASSERT_TRUE(requestOpt);                                                            \
+    EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
+    EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
+
+namespace android::display {
+namespace {
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+class DisplayModeControllerTest : public testing::Test {
+public:
+    using Action = DisplayModeController::DesiredModeAction;
+
+    void SetUp() override {
+        mDmc.setHwComposer(mComposer.get());
+        mDmc.setActiveModeListener(
+                [this](PhysicalDisplayId displayId, Fps vsyncRate, Fps renderFps) {
+                    mActiveModeListener.Call(displayId, vsyncRate, renderFps);
+                });
+
+        constexpr uint8_t kPort = 111;
+        EXPECT_CALL(*mComposerHal, getDisplayIdentificationData(kHwcDisplayId, _, _))
+                .WillOnce(DoAll(SetArgPointee<1>(kPort), SetArgPointee<2>(getInternalEdid()),
+                                Return(hal::Error::NONE)));
+
+        EXPECT_CALL(*mComposerHal, setClientTargetSlotCount(kHwcDisplayId));
+        EXPECT_CALL(*mComposerHal,
+                    setVsyncEnabled(kHwcDisplayId, hal::IComposerClient::Vsync::DISABLE));
+        EXPECT_CALL(*mComposerHal, onHotplugConnect(kHwcDisplayId));
+
+        const auto infoOpt = mComposer->onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+        ASSERT_TRUE(infoOpt);
+
+        mDisplayId = infoOpt->id;
+        mDisplaySnapshotOpt.emplace(mDisplayId, ui::DisplayConnectionType::Internal,
+                                    makeModes(kMode60, kMode90, kMode120), ui::ColorModes{},
+                                    std::nullopt);
+
+        ftl::FakeGuard guard(kMainThreadContext);
+        mDmc.registerDisplay(*mDisplaySnapshotOpt, kModeId60,
+                             scheduler::RefreshRateSelector::Config{});
+    }
+
+protected:
+    hal::VsyncPeriodChangeConstraints expectModeSet(const DisplayModeRequest& request,
+                                                    hal::VsyncPeriodChangeTimeline& timeline,
+                                                    bool subsequent = false) {
+        EXPECT_CALL(*mComposerHal,
+                    isSupported(Hwc2::Composer::OptionalFeature::RefreshRateSwitching))
+                .WillOnce(Return(true));
+
+        if (!subsequent) {
+            EXPECT_CALL(*mComposerHal, getDisplayConnectionType(kHwcDisplayId, _))
+                    .WillOnce(DoAll(SetArgPointee<1>(
+                                            hal::IComposerClient::DisplayConnectionType::INTERNAL),
+                                    Return(hal::V2_4::Error::NONE)));
+        }
+
+        const hal::VsyncPeriodChangeConstraints constraints{
+                .desiredTimeNanos = systemTime(),
+                .seamlessRequired = false,
+        };
+
+        const hal::HWConfigId hwcModeId = request.mode.modePtr->getHwcId();
+
+        EXPECT_CALL(*mComposerHal,
+                    setActiveConfigWithConstraints(kHwcDisplayId, hwcModeId, constraints, _))
+                .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(hal::V2_4::Error::NONE)));
+
+        return constraints;
+    }
+
+    static constexpr hal::HWDisplayId kHwcDisplayId = 1234;
+
+    Hwc2::mock::Composer* mComposerHal = new testing::StrictMock<Hwc2::mock::Composer>();
+    const std::unique_ptr<HWComposer> mComposer{
+            std::make_unique<impl::HWComposer>(std::unique_ptr<Hwc2::Composer>(mComposerHal))};
+
+    testing::MockFunction<void(PhysicalDisplayId, Fps, Fps)> mActiveModeListener;
+
+    DisplayModeController mDmc;
+
+    PhysicalDisplayId mDisplayId;
+    std::optional<DisplaySnapshot> mDisplaySnapshotOpt;
+
+    static constexpr DisplayModeId kModeId60{0};
+    static constexpr DisplayModeId kModeId90{1};
+    static constexpr DisplayModeId kModeId120{2};
+
+    static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId60, 60_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId90, 90_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+            ftl::as_non_null(mock::createDisplayMode(kModeId120, 120_Hz));
+
+    static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
+    static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
+    static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
+};
+
+TEST_F(DisplayModeControllerTest, setDesiredModeToActiveMode) {
+    EXPECT_CALL(mActiveModeListener, Call(_, _, _)).Times(0);
+
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode60)));
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, setDesiredMode) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+
+    // No action since a mode switch has already been initiated.
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, clearDesiredMode) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_TRUE(mDmc.getDesiredMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateModeChange) REQUIRES(kMainThreadContext) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+    auto modeRequest = kDesiredMode90;
+
+    hal::VsyncPeriodChangeTimeline timeline;
+    const auto constraints = expectModeSet(modeRequest, timeline);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateRenderRateSwitch) {
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 30_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateRenderRateSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode30)));
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+TEST_F(DisplayModeControllerTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
+    // Called because setDesiredMode resets the render rate to the active refresh rate.
+    EXPECT_CALL(mActiveModeListener, Call(mDisplayId, 60_Hz, 60_Hz)).Times(1);
+
+    EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+              mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode90)));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getDesiredMode(mDisplayId));
+    auto modeRequest = kDesiredMode90;
+
+    hal::VsyncPeriodChangeTimeline timeline;
+    auto constraints = expectModeSet(modeRequest, timeline);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+
+    // No action since a mode switch has already been initiated.
+    EXPECT_EQ(Action::None, mDmc.setDesiredMode(mDisplayId, DisplayModeRequest(kDesiredMode120)));
+
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDmc.getPendingMode(mDisplayId));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getDesiredMode(mDisplayId));
+    modeRequest = kDesiredMode120;
+
+    constexpr bool kSubsequent = true;
+    constraints = expectModeSet(modeRequest, timeline, kSubsequent);
+
+    EXPECT_TRUE(mDmc.initiateModeChange(mDisplayId, std::move(modeRequest), constraints, timeline));
+    EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDmc.getPendingMode(mDisplayId));
+
+    mDmc.clearDesiredMode(mDisplayId);
+    EXPECT_FALSE(mDmc.getDesiredMode(mDisplayId));
+}
+
+} // namespace
+} // namespace android::display
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fa31643..9b10c94 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -64,17 +64,6 @@
 
 void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(mFlinger.scheduler());
-
-    EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(
-                    sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
-                                                             mock::EventThread::kCallingUid)));
-
     mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
                             std::make_shared<mock::VSyncTracker>(),
                             std::unique_ptr<EventThread>(mEventThread),
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index f26336a..db3c0a1 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -498,9 +498,7 @@
 constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
 
 template <typename PhysicalDisplay, int width, int height,
-          Secure secure = (PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal)
-                  ? Secure::TRUE
-                  : Secure::FALSE>
+          Secure secure = (PhysicalDisplay::SECURE) ? Secure::TRUE : Secure::FALSE>
 struct PhysicalDisplayVariant
       : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE, secure,
                        PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
@@ -515,16 +513,18 @@
 struct PrimaryDisplay {
     static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::Internal;
     static constexpr Primary PRIMARY = Primary::TRUE;
+    static constexpr bool SECURE = true;
     static constexpr uint8_t PORT = 255;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1001;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
     static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
 };
 
-template <ui::DisplayConnectionType connectionType, bool hasIdentificationData>
+template <ui::DisplayConnectionType connectionType, bool hasIdentificationData, bool secure>
 struct SecondaryDisplay {
     static constexpr auto CONNECTION_TYPE = connectionType;
     static constexpr Primary PRIMARY = Primary::FALSE;
+    static constexpr bool SECURE = secure;
     static constexpr uint8_t PORT = 254;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1002;
     static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
@@ -533,9 +533,14 @@
                                                                   : getExternalEdid;
 };
 
+constexpr bool kSecure = true;
+constexpr bool kNonSecure = false;
+
+template <bool secure>
 struct TertiaryDisplay {
     static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
     static constexpr Primary PRIMARY = Primary::FALSE;
+    static constexpr bool SECURE = secure;
     static constexpr uint8_t PORT = 253;
     static constexpr HWDisplayId HWC_DISPLAY_ID = 1003;
     static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
@@ -545,14 +550,26 @@
 
 using InnerDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<true>, 1840, 2208>;
 using OuterDisplayVariant =
-        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal, true>, 1080,
-                               2092>;
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal,
+                                                /*hasIdentificationData=*/true, kSecure>,
+                               1080, 2092>;
+using OuterDisplayNonSecureVariant =
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal,
+                                                /*hasIdentificationData=*/true, kNonSecure>,
+                               1080, 2092>;
 
 using ExternalDisplayVariant =
-        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External, false>, 1920,
-                               1280>;
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
+                                                /*hasIdentificationData=*/false, kSecure>,
+                               1920, 1280>;
+using ExternalDisplayNonSecureVariant =
+        PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External,
+                                                /*hasIdentificationData=*/false, kNonSecure>,
+                               1920, 1280>;
 
-using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200>;
+using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay<kSecure>, 1600, 1200>;
+using TertiaryDisplayNonSecureVariant =
+        PhysicalDisplayVariant<TertiaryDisplay<kNonSecure>, 1600, 1200>;
 
 // A virtual display not supported by the HWC.
 constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0;
@@ -750,10 +767,18 @@
         Case<ExternalDisplayVariant, WideColorNotSupportedVariant<ExternalDisplayVariant>,
              HdrNotSupportedVariant<ExternalDisplayVariant>,
              NoPerFrameMetadataSupportVariant<ExternalDisplayVariant>>;
+using SimpleExternalDisplayNonSecureCase =
+        Case<ExternalDisplayVariant, WideColorNotSupportedVariant<ExternalDisplayNonSecureVariant>,
+             HdrNotSupportedVariant<ExternalDisplayNonSecureVariant>,
+             NoPerFrameMetadataSupportVariant<ExternalDisplayNonSecureVariant>>;
 using SimpleTertiaryDisplayCase =
         Case<TertiaryDisplayVariant, WideColorNotSupportedVariant<TertiaryDisplayVariant>,
              HdrNotSupportedVariant<TertiaryDisplayVariant>,
              NoPerFrameMetadataSupportVariant<TertiaryDisplayVariant>>;
+using SimpleTertiaryDisplayNonSecureCase =
+        Case<TertiaryDisplayVariant, WideColorNotSupportedVariant<TertiaryDisplayNonSecureVariant>,
+             HdrNotSupportedVariant<TertiaryDisplayNonSecureVariant>,
+             NoPerFrameMetadataSupportVariant<TertiaryDisplayNonSecureVariant>>;
 
 using NonHwcVirtualDisplayCase =
         Case<NonHwcVirtualDisplayVariant<1024, 768, Secure::FALSE>,
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index 3eabe1f..625d2e6 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -493,7 +493,7 @@
     EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_, _))
             .WillOnce(Return(preferredExpectedPresentationTime));
 
-    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
+    VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection, now);
 
     // Check EventThread immediately requested a resync.
     EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0adf0b6..51b5f40 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -85,7 +85,7 @@
     EXPECT_EQ(false, mFlagManager.test_flag());
 }
 
-TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+TEST_F(FlagManagerTest, crashesIfQueriedBeforeBoot) {
     mFlagManager.markBootIncomplete();
     EXPECT_DEATH(FlagManager::getInstance()
         .refresh_rate_overlay_on_external_display(), "");
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
deleted file mode 100644
index d30d5b8..0000000
--- a/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/LayerMetadata.h>
-
-#include "Layer.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-
-namespace android {
-
-using testing::_;
-using testing::DoAll;
-using testing::Mock;
-using testing::Return;
-using testing::SetArgPointee;
-
-using android::Hwc2::IComposer;
-using android::Hwc2::IComposerClient;
-
-/**
- * This class covers all the test that are related to refresh rate selection.
- */
-class RefreshRateSelectionTest : public testing::Test {
-public:
-    RefreshRateSelectionTest();
-    ~RefreshRateSelectionTest() override;
-
-protected:
-    static constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
-    static constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
-    static constexpr uint32_t WIDTH = 100;
-    static constexpr uint32_t HEIGHT = 100;
-    static constexpr uint32_t LAYER_FLAGS = 0;
-    static constexpr int32_t PRIORITY_UNSET = -1;
-
-    sp<Layer> createBufferStateLayer();
-    sp<Layer> createEffectLayer();
-
-    void setParent(Layer* child, Layer* parent);
-    void commitTransaction(Layer* layer);
-
-    TestableSurfaceFlinger mFlinger;
-
-    sp<Client> mClient;
-    sp<Layer> mParent;
-    sp<Layer> mChild;
-    sp<Layer> mGrandChild;
-};
-
-RefreshRateSelectionTest::RefreshRateSelectionTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-
-    mFlinger.setupMockScheduler();
-    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-}
-
-RefreshRateSelectionTest::~RefreshRateSelectionTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-}
-
-sp<Layer> RefreshRateSelectionTest::createBufferStateLayer() {
-    sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", LAYER_FLAGS,
-                           LayerMetadata());
-    return sp<Layer>::make(args);
-}
-
-sp<Layer> RefreshRateSelectionTest::createEffectLayer() {
-    sp<Client> client;
-    LayerCreationArgs args(mFlinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
-    return sp<Layer>::make(args);
-}
-
-void RefreshRateSelectionTest::setParent(Layer* child, Layer* parent) {
-    child->setParent(sp<Layer>::fromExisting(parent));
-}
-
-void RefreshRateSelectionTest::commitTransaction(Layer* layer) {
-    layer->commitTransaction();
-}
-
-namespace {
-
-TEST_F(RefreshRateSelectionTest, testPriorityOnBufferStateLayers) {
-    mParent = createBufferStateLayer();
-    mChild = createBufferStateLayer();
-    setParent(mChild.get(), mParent.get());
-    mGrandChild = createBufferStateLayer();
-    setParent(mGrandChild.get(), mChild.get());
-
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child has its own priority.
-    mGrandChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child inherits from his parent.
-    mChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Grandchild inherits from his grand parent.
-    mParent->setFrameRateSelectionPriority(1);
-    commitTransaction(mParent.get());
-    mChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(1, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-}
-
-TEST_F(RefreshRateSelectionTest, testPriorityOnEffectLayers) {
-    mParent = createEffectLayer();
-    mChild = createEffectLayer();
-    setParent(mChild.get(), mParent.get());
-    mGrandChild = createEffectLayer();
-    setParent(mGrandChild.get(), mChild.get());
-
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child has its own priority.
-    mGrandChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(PRIORITY_UNSET, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Child inherits from his parent.
-    mChild->setFrameRateSelectionPriority(1);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(PRIORITY_UNSET, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-
-    // Grandchild inherits from his grand parent.
-    mParent->setFrameRateSelectionPriority(1);
-    commitTransaction(mParent.get());
-    mChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mChild.get());
-    mGrandChild->setFrameRateSelectionPriority(PRIORITY_UNSET);
-    commitTransaction(mGrandChild.get());
-    ASSERT_EQ(1, mParent->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mChild->getFrameRateSelectionPriority());
-    ASSERT_EQ(1, mGrandChild->getFrameRateSelectionPriority());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
deleted file mode 100644
index 5c742d7..0000000
--- a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/LayerMetadata.h>
-
-#include "Layer.h"
-#include "LayerTestUtils.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-
-namespace android {
-
-using testing::DoAll;
-using testing::Mock;
-using testing::SetArgPointee;
-
-using android::Hwc2::IComposer;
-using android::Hwc2::IComposerClient;
-
-using scheduler::LayerHistory;
-
-using FrameRate = Layer::FrameRate;
-using FrameRateCompatibility = Layer::FrameRateCompatibility;
-using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
-
-/**
- * This class tests the behaviour of Layer::setFrameRateSelectionStrategy.
- */
-class FrameRateSelectionStrategyTest : public BaseLayerTest {
-protected:
-    const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_DEFAULT = FrameRate(Fps(), FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
-
-    FrameRateSelectionStrategyTest();
-
-    void addChild(sp<Layer> layer, sp<Layer> child);
-    void removeChild(sp<Layer> layer, sp<Layer> child);
-    void commitTransaction();
-
-    std::vector<sp<Layer>> mLayers;
-};
-
-FrameRateSelectionStrategyTest::FrameRateSelectionStrategyTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-
-    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-}
-
-void FrameRateSelectionStrategyTest::addChild(sp<Layer> layer, sp<Layer> child) {
-    layer->addChild(child);
-}
-
-void FrameRateSelectionStrategyTest::removeChild(sp<Layer> layer, sp<Layer> child) {
-    layer->removeChild(child);
-}
-
-void FrameRateSelectionStrategyTest::commitTransaction() {
-    for (auto layer : mLayers) {
-        layer->commitTransaction();
-    }
-}
-
-namespace {
-
-INSTANTIATE_TEST_SUITE_P(PerLayerType, FrameRateSelectionStrategyTest,
-                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
-                                         std::make_shared<EffectLayerFactory>()),
-                         PrintToStringParamName);
-
-TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-    auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    layer->setFrameRate(FRAME_RATE_VOTE1.vote);
-    layer->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer->getDrawingState().frameRateSelectionStrategy);
-}
-
-TEST_P(FrameRateSelectionStrategyTest, SetChildOverrideChildren) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
-    child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              parent->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              child1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              child2->getDrawingState().frameRateSelectionStrategy);
-}
-
-TEST_P(FrameRateSelectionStrategyTest, SetParentOverrideChildren) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-    auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    addChild(layer1, layer2);
-    addChild(layer2, layer3);
-
-    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
-    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
-    layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
-    layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
-    layer3->setFrameRate(FRAME_RATE_VOTE3.vote);
-    commitTransaction();
-
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer2->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer3->getDrawingState().frameRateSelectionStrategy);
-
-    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Propagate);
-    commitTransaction();
-
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer2->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer3->getDrawingState().frameRateSelectionStrategy);
-}
-
-TEST_P(FrameRateSelectionStrategyTest, OverrideChildrenAndSelf) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-    auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    addChild(layer1, layer2);
-    addChild(layer2, layer3);
-
-    layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
-    layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
-    layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
-    commitTransaction();
-
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
-              layer2->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_DEFAULT, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer3->getDrawingState().frameRateSelectionStrategy);
-
-    layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
-    commitTransaction();
-
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
-              layer2->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer3->getDrawingState().frameRateSelectionStrategy);
-
-    layer1->setFrameRate(FRAME_RATE_DEFAULT.vote);
-    commitTransaction();
-
-    EXPECT_EQ(FRAME_RATE_TREE, layer1->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
-              layer1->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Self,
-              layer2->getDrawingState().frameRateSelectionStrategy);
-    EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
-    EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
-              layer3->getDrawingState().frameRateSelectionStrategy);
-}
-
-} // namespace
-} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index ddc3967..08e4265 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -15,6 +15,8 @@
  */
 
 #include <common/test/FlagUtils.h>
+#include "BackgroundExecutor.h"
+#include "Jank/JankTracker.h"
 #include "com_android_graphics_surfaceflinger_flags.h"
 #include "gmock/gmock-spec-builders.h"
 #include "mock/MockTimeStats.h"
@@ -82,16 +84,22 @@
 
     void SetUp() override {
         constexpr bool kUseBootTimeClock = true;
+        constexpr bool kFilterFramesBeforeTraceStarts = false;
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
-                                                               kTestThresholds, !kUseBootTimeClock);
+                                                               kTestThresholds, !kUseBootTimeClock,
+                                                               kFilterFramesBeforeTraceStarts);
         mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
         maxDisplayFrames = &mFrameTimeline->mMaxDisplayFrames;
         maxTokens = mTokenManager->kMaxTokens;
+
+        JankTracker::clearAndStartCollectingAllJankDataForTesting();
     }
 
+    void TearDown() override { JankTracker::clearAndStopCollectingAllJankDataForTesting(); }
+
     // Each tracing session can be used for a single block of Start -> Stop.
     static std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest() {
         perfetto::TraceConfig cfg;
@@ -158,6 +166,7 @@
                 a.presentTime == b.presentTime;
     }
 
+    NO_THREAD_SAFETY_ANALYSIS
     const std::map<int64_t, TimelineItem>& getPredictions() const {
         return mTokenManager->mPredictions;
     }
@@ -175,6 +184,16 @@
                 [&](FrameTimelineDataSource::TraceContext ctx) { ctx.Flush(); });
     }
 
+    std::vector<gui::JankData> getLayerOneJankData() {
+        BackgroundExecutor::getLowPriorityInstance().flushQueue();
+        return JankTracker::getCollectedJankDataForTesting(sLayerIdOne);
+    }
+
+    std::vector<gui::JankData> getLayerTwoJankData() {
+        BackgroundExecutor::getLowPriorityInstance().flushQueue();
+        return JankTracker::getCollectedJankDataForTesting(sLayerIdTwo);
+    }
+
     std::shared_ptr<mock::TimeStats> mTimeStats;
     std::unique_ptr<impl::FrameTimeline> mFrameTimeline;
     impl::TokenManager* mTokenManager;
@@ -339,6 +358,60 @@
     EXPECT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
     EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
     EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
+
+    EXPECT_EQ(getLayerOneJankData().size(), 1u);
+    EXPECT_EQ(getLayerTwoJankData().size(), 1u);
+}
+
+TEST_F(FrameTimelineTest, displayFrameSkippedComposition) {
+    // Layer specific increment
+    EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(1);
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+    FrameTimelineInfo ftInfo;
+    ftInfo.vsyncId = surfaceFrameToken1;
+    ftInfo.inputEventId = sInputEventId;
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo,
+                                                       sLayerNameTwo, sLayerNameTwo,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->onCommitNotComposited();
+
+    EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 30);
+    ASSERT_NE(surfaceFrame1->getJankType(), std::nullopt);
+    EXPECT_EQ(*surfaceFrame1->getJankType(), JankType::None);
+    ASSERT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
+    EXPECT_EQ(*surfaceFrame1->getJankSeverityType(), JankSeverityType::None);
+
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(26, presentFence1);
+
+    auto displayFrame = getDisplayFrame(0);
+    auto& presentedSurfaceFrame2 = getSurfaceFrame(0, 0);
+    presentFence1->signalForTest(42);
+
+    // Fences haven't been flushed yet, so it should be 0
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+    EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 0);
+
+    addEmptyDisplayFrame();
+
+    // Fences have flushed, so the present timestamps should be updated
+    EXPECT_EQ(displayFrame->getActuals().presentTime, 42);
+    EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42);
+    EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
+    EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
 }
 
 TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
@@ -395,6 +468,8 @@
     // The window should have slided by 1 now and the previous 0th display frame
     // should have been removed from the deque
     EXPECT_EQ(compareTimelineItems(displayFrame0->getActuals(), TimelineItem(52, 57, 62)), true);
+
+    EXPECT_EQ(getLayerOneJankData().size(), *maxDisplayFrames);
 }
 
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceAfterQueue) {
@@ -407,6 +482,16 @@
     EXPECT_EQ(surfaceFrame->getActuals().endTime, 456);
 }
 
+TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceUnsignaled) {
+    auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
+                                                                   "acquireFenceAfterQueue",
+                                                                   "acquireFenceAfterQueue",
+                                                                   /*isBuffer*/ true, sGameMode);
+    surfaceFrame->setActualQueueTime(123);
+    surfaceFrame->setAcquireFenceTime(Fence::SIGNAL_TIME_PENDING);
+    EXPECT_EQ(surfaceFrame->getActuals().endTime, 123);
+}
+
 TEST_F(FrameTimelineTest, surfaceFrameEndTimeAcquireFenceBeforeQueue) {
     auto surfaceFrame = mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, 0, sLayerIdOne,
                                                                    "acquireFenceAfterQueue",
@@ -524,6 +609,8 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(59, presentFence1);
+
+    EXPECT_EQ(getLayerOneJankData().size(), 0u);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
@@ -552,6 +639,10 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(62, presentFence1);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerCpuDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
@@ -582,6 +673,10 @@
     presentFence1->signalForTest(70);
 
     mFrameTimeline->setSfPresent(59, presentFence1, gpuFence1);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerGpuDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
@@ -610,6 +705,10 @@
     mFrameTimeline->setSfPresent(56, presentFence1);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::DisplayHAL);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
@@ -640,6 +739,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
@@ -670,6 +773,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::SurfaceFlingerScheduling);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
@@ -700,6 +807,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::PredictionError);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
@@ -731,6 +842,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::BufferStuffing);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
@@ -763,6 +878,10 @@
 
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::AppDeadlineMissed);
 }
 
 TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
@@ -807,6 +926,10 @@
     EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 90);
     EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown | JankType::AppDeadlineMissed);
     EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::Unknown | JankType::AppDeadlineMissed);
 }
 
 /*
@@ -1081,6 +1204,72 @@
     EXPECT_EQ(received.cookie(), source.cookie());
 }
 
+TEST_F(FrameTimelineTest, traceDisplayFrameNoSkipped) {
+    // setup 2 display frames
+    // DF 1: [22, 30] -> [0, 11]
+    // DF 2: [82, 90] -> SF [5, 16]
+    auto tracingSession = getTracingSessionForTest();
+    tracingSession->StartBlocking();
+    int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+    int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 100});
+    int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({0, 11, 25});
+    int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({5, 16, 30});
+    auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+    int64_t traceCookie = snoopCurrentTraceCookie();
+
+    // set up 1st display frame
+    FrameTimelineInfo ftInfo1;
+    ftInfo1.vsyncId = surfaceFrameToken1;
+    ftInfo1.inputEventId = sInputEventId;
+    auto surfaceFrame1 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo1, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+    surfaceFrame1->setAcquireFenceTime(11);
+    mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_30);
+    surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+    mFrameTimeline->setSfPresent(30, presentFence1);
+    presentFence1->signalForTest(40);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+    FrameTimelineInfo ftInfo2;
+    ftInfo2.vsyncId = surfaceFrameToken2;
+    ftInfo2.inputEventId = sInputEventId;
+    auto surfaceFrame2 =
+            mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+                                                       sLayerNameOne, sLayerNameOne,
+                                                       /*isBuffer*/ true, sGameMode);
+
+    // set up 2nd display frame
+    surfaceFrame2->setAcquireFenceTime(16);
+    mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_11, RR_30);
+    surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+    mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+    mFrameTimeline->setSfPresent(90, presentFence2);
+    presentFence2->signalForTest(100);
+
+    // the token of skipped Display Frame
+    auto protoSkippedActualDisplayFrameStart =
+            createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid,
+                                               FrameTimelineEvent::PRESENT_DROPPED, true, false,
+                                               FrameTimelineEvent::JANK_DROPPED,
+                                               FrameTimelineEvent::SEVERITY_NONE,
+                                               FrameTimelineEvent::PREDICTION_VALID);
+    auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9);
+
+    // Trigger a flush by finalizing the next DisplayFrame
+    addEmptyDisplayFrame();
+    flushTrace();
+    tracingSession->StopBlocking();
+
+    auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+    // 8 Valid Display Frames + 8 Valid Surface Frames + no Skipped Display Frames
+    EXPECT_EQ(packets.size(), 16u);
+}
+
 TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) {
     SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
                       true);
@@ -1672,6 +1861,10 @@
     EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
     EXPECT_EQ(displayFrame->getJankType(), JankType::None);
     EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
+
+    auto jankData = getLayerOneJankData();
+    EXPECT_EQ(jankData.size(), 1u);
+    EXPECT_EQ(jankData[0].jankType, JankType::None);
 }
 
 TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
deleted file mode 100644
index 1b5c6e7..0000000
--- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/LayerMetadata.h>
-#include <gui/SurfaceComposerClient.h>
-#include <log/log.h>
-
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-
-namespace android {
-
-using testing::_;
-using testing::Mock;
-using testing::Return;
-
-using gui::GameMode;
-using gui::LayerMetadata;
-
-class GameModeTest : public testing::Test {
-public:
-    GameModeTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-        mFlinger.setupMockScheduler();
-        setupComposer();
-    }
-
-    ~GameModeTest() {
-        const ::testing::TestInfo* const test_info =
-                ::testing::UnitTest::GetInstance()->current_test_info();
-        ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
-    }
-
-    sp<Layer> createLayer() {
-        sp<Client> client;
-        LayerCreationArgs args(mFlinger.flinger(), client, "layer", 0, LayerMetadata());
-        return sp<Layer>::make(args);
-    }
-
-    void setupComposer() {
-        mComposer = new Hwc2::mock::Composer();
-        mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
-
-        Mock::VerifyAndClear(mComposer);
-    }
-
-    // Mocks the behavior of applying a transaction from WMShell
-    void setGameModeMetadata(sp<Layer> layer, GameMode gameMode) {
-        mLayerMetadata.setInt32(gui::METADATA_GAME_MODE, static_cast<int32_t>(gameMode));
-        layer->setMetadata(mLayerMetadata);
-        layer->setGameModeForTree(gameMode);
-    }
-
-    TestableSurfaceFlinger mFlinger;
-    Hwc2::mock::Composer* mComposer = nullptr;
-    client_cache_t mClientCache;
-    LayerMetadata mLayerMetadata;
-};
-
-TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) {
-    sp<Layer> rootLayer = createLayer();
-    sp<Layer> childLayer1 = createLayer();
-    sp<Layer> childLayer2 = createLayer();
-    rootLayer->addChild(childLayer1);
-    rootLayer->addChild(childLayer2);
-    rootLayer->setGameModeForTree(GameMode::Performance);
-
-    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
-    EXPECT_EQ(childLayer1->getGameMode(), GameMode::Performance);
-    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
-}
-
-TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) {
-    sp<Layer> rootLayer = createLayer();
-    sp<Layer> childLayer = createLayer();
-    rootLayer->setGameModeForTree(GameMode::Performance);
-    rootLayer->addChild(childLayer);
-
-    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
-    EXPECT_EQ(childLayer->getGameMode(), GameMode::Performance);
-}
-
-TEST_F(GameModeTest, RemoveChildResetsGameMode) {
-    sp<Layer> rootLayer = createLayer();
-    sp<Layer> childLayer = createLayer();
-    rootLayer->setGameModeForTree(GameMode::Performance);
-    rootLayer->addChild(childLayer);
-
-    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Performance);
-    EXPECT_EQ(childLayer->getGameMode(), GameMode::Performance);
-
-    rootLayer->removeChild(childLayer);
-    EXPECT_EQ(childLayer->getGameMode(), GameMode::Unsupported);
-}
-
-TEST_F(GameModeTest, ReparentingDoesNotOverrideMetadata) {
-    sp<Layer> rootLayer = createLayer();
-    sp<Layer> childLayer1 = createLayer();
-    sp<Layer> childLayer2 = createLayer();
-    rootLayer->setGameModeForTree(GameMode::Standard);
-    rootLayer->addChild(childLayer1);
-
-    setGameModeMetadata(childLayer2, GameMode::Performance);
-    rootLayer->addChild(childLayer2);
-
-    EXPECT_EQ(rootLayer->getGameMode(), GameMode::Standard);
-    EXPECT_EQ(childLayer1->getGameMode(), GameMode::Standard);
-    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
-
-    rootLayer->removeChild(childLayer2);
-    EXPECT_EQ(childLayer2->getGameMode(), GameMode::Performance);
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 2cff2f2..e0753a3 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -58,6 +58,7 @@
 
 using Hwc2::Config;
 
+using ::aidl::android::hardware::drm::HdcpLevels;
 using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
 using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
 using hal::IComposerClient;
@@ -165,6 +166,7 @@
     expectHotplugConnect(kHwcDisplayId);
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
+    ASSERT_TRUE(info->preferredDetailedTimingDescriptor.has_value());
 
     EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
@@ -178,6 +180,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
 
         EXPECT_CALL(*mHal,
                     getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
@@ -217,8 +223,13 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Optional parameters are supported
         constexpr int32_t kDpi = 320;
@@ -270,6 +281,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
 
         EXPECT_CALL(*mHal,
                     getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
@@ -309,8 +324,13 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Optional parameters are supported
         constexpr int32_t kDpi = 320;
@@ -360,6 +380,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
@@ -386,8 +410,13 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Supports optional dpi parameter
         constexpr int32_t kDpi = 320;
@@ -454,6 +483,8 @@
     MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
     MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
     MOCK_METHOD(void, onRefreshRateChangedDebug, (const RefreshRateChangedDebugData&), (override));
+    MOCK_METHOD(void, onComposerHalHdcpLevelsChanged, (hal::HWDisplayId, const HdcpLevels&),
+                (override));
 };
 
 struct HWComposerSetCallbackTest : HWComposerTest {
diff --git a/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
new file mode 100644
index 0000000..2941a14
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/JankTrackerTest.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/gui/BnJankListener.h>
+#include <binder/IInterface.h>
+#include "BackgroundExecutor.h"
+#include "Jank/JankTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+
+namespace {
+
+using namespace testing;
+
+class MockJankListener : public gui::BnJankListener {
+public:
+    MockJankListener() = default;
+    ~MockJankListener() override = default;
+
+    MOCK_METHOD(binder::Status, onJankData, (const std::vector<gui::JankData>& jankData),
+                (override));
+};
+
+} // anonymous namespace
+
+class JankTrackerTest : public Test {
+public:
+    JankTrackerTest() {}
+
+    void SetUp() override { mListener = sp<StrictMock<MockJankListener>>::make(); }
+
+    void addJankListener(int32_t layerId) {
+        JankTracker::addJankListener(layerId, IInterface::asBinder(mListener));
+    }
+
+    void removeJankListener(int32_t layerId, int64_t after) {
+        JankTracker::removeJankListener(layerId, IInterface::asBinder(mListener), after);
+    }
+
+    void addJankData(int32_t layerId, int jankType) {
+        gui::JankData data;
+        data.frameVsyncId = mVsyncId++;
+        data.jankType = jankType;
+        data.frameIntervalNs = 8333333;
+        JankTracker::onJankData(layerId, data);
+    }
+
+    void flushBackgroundThread() { BackgroundExecutor::getLowPriorityInstance().flushQueue(); }
+
+    size_t listenerCount() { return JankTracker::sListenerCount; }
+
+    std::vector<gui::JankData> getCollectedJankData(int32_t layerId) {
+        return JankTracker::getCollectedJankDataForTesting(layerId);
+    }
+
+    sp<StrictMock<MockJankListener>> mListener = nullptr;
+    int64_t mVsyncId = 1000;
+};
+
+TEST_F(JankTrackerTest, jankDataIsTrackedAndPropagated) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
+            .WillOnce([](const std::vector<gui::JankData>& jankData) {
+                EXPECT_EQ(jankData[0].frameVsyncId, 1000);
+                EXPECT_EQ(jankData[0].jankType, 1);
+                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[1].frameVsyncId, 1001);
+                EXPECT_EQ(jankData[1].jankType, 2);
+                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[2].frameVsyncId, 1002);
+                EXPECT_EQ(jankData[2].jankType, 3);
+                EXPECT_EQ(jankData[2].frameIntervalNs, 8333333);
+                return binder::Status::ok();
+            });
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(2)))
+            .WillOnce([](const std::vector<gui::JankData>& jankData) {
+                EXPECT_EQ(jankData[0].frameVsyncId, 1003);
+                EXPECT_EQ(jankData[0].jankType, 4);
+                EXPECT_EQ(jankData[0].frameIntervalNs, 8333333);
+
+                EXPECT_EQ(jankData[1].frameVsyncId, 1004);
+                EXPECT_EQ(jankData[1].jankType, 5);
+                EXPECT_EQ(jankData[1].frameIntervalNs, 8333333);
+
+                return binder::Status::ok();
+            });
+
+    addJankListener(123);
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    JankTracker::flushJankData(123);
+    addJankData(123, 4);
+    removeJankListener(123, mVsyncId);
+    addJankData(123, 5);
+    JankTracker::flushJankData(123);
+    addJankData(123, 6);
+    JankTracker::flushJankData(123);
+    removeJankListener(123, 0);
+
+    flushBackgroundThread();
+}
+
+TEST_F(JankTrackerTest, jankDataIsAutomaticallyFlushedInBatches) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    // needs to be larger than kJankDataBatchSize in JankTracker.cpp.
+    constexpr size_t kNumberOfJankDataToSend = 234;
+
+    size_t jankDataReceived = 0;
+    size_t numBatchesReceived = 0;
+
+    EXPECT_CALL(*mListener.get(), onJankData(_))
+            .WillRepeatedly([&](const std::vector<gui::JankData>& jankData) {
+                jankDataReceived += jankData.size();
+                numBatchesReceived++;
+                return binder::Status::ok();
+            });
+
+    addJankListener(123);
+    for (size_t i = 0; i < kNumberOfJankDataToSend; i++) {
+        addJankData(123, 0);
+    }
+
+    flushBackgroundThread();
+    // Check that we got some data, without explicitly flushing.
+    EXPECT_GT(jankDataReceived, 0u);
+    EXPECT_GT(numBatchesReceived, 0u);
+    EXPECT_LT(numBatchesReceived, jankDataReceived); // batches should be > size 1.
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+    EXPECT_EQ(jankDataReceived, kNumberOfJankDataToSend);
+}
+
+TEST_F(JankTrackerTest, jankListenerIsRemovedWhenReturningNullError) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    EXPECT_CALL(*mListener.get(), onJankData(SizeIs(3)))
+            .WillOnce(Return(binder::Status::fromExceptionCode(binder::Status::EX_NULL_POINTER)));
+
+    addJankListener(123);
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    JankTracker::flushJankData(123);
+    addJankData(123, 4);
+    addJankData(123, 5);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+TEST_F(JankTrackerTest, jankDataIsDroppedIfNobodyIsListening) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankData(123, 1);
+    addJankData(123, 2);
+    addJankData(123, 3);
+    flushBackgroundThread();
+
+    EXPECT_EQ(getCollectedJankData(123).size(), 0u);
+}
+
+TEST_F(JankTrackerTest, listenerCountTracksRegistrations) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankListener(123);
+    addJankListener(456);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 2u);
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    removeJankListener(456, 0);
+    JankTracker::flushJankData(456);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+TEST_F(JankTrackerTest, listenerCountIsAccurateOnDuplicateRegistration) {
+    ASSERT_EQ(listenerCount(), 0u);
+
+    addJankListener(123);
+    addJankListener(123);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 1u);
+
+    removeJankListener(123, 0);
+    JankTracker::flushJankData(123);
+    flushBackgroundThread();
+    EXPECT_EQ(listenerCount(), 0u);
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
index 2b333f4..b79bdb4 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -777,4 +777,28 @@
     EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
 }
 
+// (b/343901186)
+TEST_F(LayerHierarchyTest, cleanUpDanglingMirrorLayer) {
+    LayerHierarchyBuilder hierarchyBuilder;
+    hierarchyBuilder.update(mLifecycleManager);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+
+    std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 2, 2};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+    // destroy layer handle
+    reparentLayer(2, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(2);
+    UPDATE_AND_VERIFY(hierarchyBuilder);
+    expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+    expectedTraversalPath = {};
+    EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
index 67e6249..37cda3e 100644
--- a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -25,13 +25,15 @@
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHierarchy.h"
 #include "FrontEnd/LayerLifecycleManager.h"
+#include "LayerLifecycleManagerHelper.h"
+
 #include "FrontEnd/LayerSnapshotBuilder.h"
 
 namespace android::surfaceflinger::frontend {
 
-class LayerHierarchyTestBase : public testing::Test {
+class LayerHierarchyTestBase : public testing::Test, public LayerLifecycleManagerHelper {
 protected:
-    LayerHierarchyTestBase() {
+    LayerHierarchyTestBase() : LayerLifecycleManagerHelper(mLifecycleManager) {
         // tree with 3 levels of children
         // ROOT
         // ├── 1
@@ -55,24 +57,6 @@
         createLayer(1221, 122);
     }
 
-    LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
-                                 uint32_t layerIdToMirror) {
-        LayerCreationArgs args(std::make_optional(id));
-        args.name = "testlayer";
-        args.addToRoot = canBeRoot;
-        args.parentId = parentId;
-        args.layerIdToMirror = layerIdToMirror;
-        return args;
-    }
-
-    LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStackToMirror) {
-        LayerCreationArgs args(std::make_optional(id));
-        args.name = "testlayer";
-        args.addToRoot = true;
-        args.layerStackToMirror = layerStackToMirror;
-        return args;
-    }
-
     std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
         std::vector<uint32_t> layerIds;
         hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
@@ -94,87 +78,6 @@
         return layerIds;
     }
 
-    virtual void createRootLayer(uint32_t id) {
-        std::vector<std::unique_ptr<RequestedLayerState>> layers;
-        layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
-                           /*mirror=*/UNASSIGNED_LAYER_ID)));
-        mLifecycleManager.addLayers(std::move(layers));
-    }
-
-    void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
-        std::vector<std::unique_ptr<RequestedLayerState>> layers;
-        layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createDisplayMirrorArgs(/*id=*/id, layerStack)));
-        mLifecycleManager.addLayers(std::move(layers));
-    }
-
-    virtual void createLayer(uint32_t id, uint32_t parentId) {
-        std::vector<std::unique_ptr<RequestedLayerState>> layers;
-        layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
-                           /*mirror=*/UNASSIGNED_LAYER_ID)));
-        mLifecycleManager.addLayers(std::move(layers));
-    }
-
-    std::vector<TransactionState> reparentLayerTransaction(uint32_t id, uint32_t newParentId) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-        transactions.back().states.front().parentId = newParentId;
-        transactions.back().states.front().state.what = layer_state_t::eReparent;
-        transactions.back().states.front().relativeParentId = UNASSIGNED_LAYER_ID;
-        transactions.back().states.front().layerId = id;
-        return transactions;
-    }
-
-    void reparentLayer(uint32_t id, uint32_t newParentId) {
-        mLifecycleManager.applyTransactions(reparentLayerTransaction(id, newParentId));
-    }
-
-    std::vector<TransactionState> relativeLayerTransaction(uint32_t id, uint32_t relativeParentId) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-        transactions.back().states.front().relativeParentId = relativeParentId;
-        transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
-        transactions.back().states.front().layerId = id;
-        return transactions;
-    }
-
-    void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
-        mLifecycleManager.applyTransactions(relativeLayerTransaction(id, relativeParentId));
-    }
-
-    void removeRelativeZ(uint32_t id) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
-        transactions.back().states.front().layerId = id;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    virtual void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
-        std::vector<std::unique_ptr<RequestedLayerState>> layers;
-        layers.emplace_back(std::make_unique<RequestedLayerState>(
-                createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
-                           /*mirror=*/layerIdToMirror)));
-        mLifecycleManager.addLayers(std::move(layers));
-    }
-
-    void updateBackgroundColor(uint32_t id, half alpha) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-        transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
-        transactions.back().states.front().state.bgColor.a = alpha;
-        transactions.back().states.front().layerId = id;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
-
     void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
         hierarchyBuilder.update(mLifecycleManager);
         mLifecycleManager.commitChanges();
@@ -190,303 +93,6 @@
                 mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
     }
 
-    std::vector<TransactionState> setZTransaction(uint32_t id, int32_t z) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.z = z;
-        return transactions;
-    }
-
-    void setZ(uint32_t id, int32_t z) {
-        mLifecycleManager.applyTransactions(setZTransaction(id, z));
-    }
-
-    void setCrop(uint32_t id, const Rect& crop) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eCropChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.crop = crop;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
-        transactions.back().states.front().state.flags = flags;
-        transactions.back().states.front().state.mask = mask;
-        transactions.back().states.front().layerId = id;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setAlpha(uint32_t id, float alpha) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.color.a = static_cast<half>(alpha);
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void hideLayer(uint32_t id) {
-        setFlags(id, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
-    }
-
-    void showLayer(uint32_t id) { setFlags(id, layer_state_t::eLayerHidden, 0); }
-
-    void setColor(uint32_t id, half3 rgb = half3(1._hf, 1._hf, 1._hf)) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-        transactions.back().states.front().state.what = layer_state_t::eColorChanged;
-        transactions.back().states.front().state.color.rgb = rgb;
-        transactions.back().states.front().layerId = id;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setLayerStack(uint32_t id, int32_t layerStack) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eLayerStackChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.layerStack = ui::LayerStack::fromValue(layerStack);
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setTouchableRegion(uint32_t id, Region region) {
-        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->token = sp<BBinder>::make();
-        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);
-    }
-
-    void setFrameRateSelectionPriority(uint32_t id, int32_t priority) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eFrameRateSelectionPriority;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.frameRateSelectionPriority = priority;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setFrameRate(uint32_t id, float frameRate, int8_t compatibility,
-                      int8_t changeFrameRateStrategy) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.frameRate = frameRate;
-        transactions.back().states.front().state.frameRateCompatibility = compatibility;
-        transactions.back().states.front().state.changeFrameRateStrategy = changeFrameRateStrategy;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setFrameRateCategory(uint32_t id, int8_t frameRateCategory) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eFrameRateCategoryChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.frameRateCategory = frameRateCategory;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what =
-                layer_state_t::eFrameRateSelectionStrategyChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setDefaultFrameRateCompatibility(uint32_t id, int8_t defaultFrameRateCompatibility) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what =
-                layer_state_t::eDefaultFrameRateCompatibilityChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.defaultFrameRateCompatibility =
-                defaultFrameRateCompatibility;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setRoundedCorners(uint32_t id, float radius) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eCornerRadiusChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.cornerRadius = radius;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setBuffer(uint32_t id, std::shared_ptr<renderengine::ExternalTexture> texture) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eBufferChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().externalTexture = texture;
-        transactions.back().states.front().state.bufferData =
-                std::make_shared<fake::BufferData>(texture->getId(), texture->getWidth(),
-                                                   texture->getHeight(), texture->getPixelFormat(),
-                                                   texture->getUsage());
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setBuffer(uint32_t id) {
-        static uint64_t sBufferId = 1;
-        setBuffer(id,
-                  std::make_shared<renderengine::mock::
-                                           FakeExternalTexture>(1U /*width*/, 1U /*height*/,
-                                                                sBufferId++,
-                                                                HAL_PIXEL_FORMAT_RGBA_8888,
-                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
-    }
-
-    void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.bufferCrop = bufferCrop;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setDamageRegion(uint32_t id, const Region& damageRegion) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setDataspace(uint32_t id, ui::Dataspace dataspace) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eDataspaceChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.dataspace = dataspace;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
-        layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
-
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.matrix = matrix;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setShadowRadius(uint32_t id, float shadowRadius) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eShadowRadiusChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.shadowRadius = shadowRadius;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
-    void setDropInputMode(uint32_t id, gui::DropInputMode dropInputMode) {
-        std::vector<TransactionState> transactions;
-        transactions.emplace_back();
-        transactions.back().states.push_back({});
-
-        transactions.back().states.front().state.what = layer_state_t::eDropInputModeChanged;
-        transactions.back().states.front().layerId = id;
-        transactions.back().states.front().state.dropInputMode = dropInputMode;
-        mLifecycleManager.applyTransactions(transactions);
-    }
-
     LayerLifecycleManager mLifecycleManager;
 };
 
@@ -494,21 +100,6 @@
 protected:
     LayerSnapshotTestBase() : LayerHierarchyTestBase() {}
 
-    void createRootLayer(uint32_t id) override {
-        LayerHierarchyTestBase::createRootLayer(id);
-        setColor(id);
-    }
-
-    void createLayer(uint32_t id, uint32_t parentId) override {
-        LayerHierarchyTestBase::createLayer(id, parentId);
-        setColor(parentId);
-    }
-
-    void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
-        LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
-        setColor(id);
-    }
-
     void update(LayerSnapshotBuilder& snapshotBuilder) {
         mHierarchyBuilder.update(mLifecycleManager);
         LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
index 110f324..de37b63 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -38,6 +38,7 @@
 namespace android::scheduler {
 
 using android::mock::createDisplayMode;
+using android::mock::createVrrDisplayMode;
 using namespace com::android::graphics::surfaceflinger;
 
 class LayerHistoryIntegrationTest : public surfaceflinger::frontend::LayerSnapshotTestBase {
@@ -53,6 +54,8 @@
     static constexpr Fps HI_FPS = 90_Hz;
     static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
 
+    static constexpr auto kVrrModeId = DisplayModeId(2);
+
     LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
         mFlinger.resetScheduler(mScheduler);
         mLifecycleManager = {};
@@ -71,6 +74,13 @@
         updateLayerSnapshotsAndLayerHistory(time);
     }
 
+    void setFrontBufferWithPresentTime(sp<Layer>& layer, nsecs_t time) {
+        uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+        setFrontBuffer(sequence);
+        layer->setDesiredPresentTime(time, false /*autotimestamp*/);
+        updateLayerSnapshotsAndLayerHistory(time);
+    }
+
     LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
     const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
 
@@ -135,6 +145,21 @@
         return layer;
     }
 
+    auto createLegacyAndFrontedEndLayerWithUid(uint32_t sequence, gui::Uid uid) {
+        std::string layerName = "test layer:" + std::to_string(sequence);
+        auto args = LayerCreationArgs{mFlinger.flinger(),
+                                      nullptr,
+                                      layerName,
+                                      0,
+                                      {},
+                                      std::make_optional<uint32_t>(sequence)};
+        args.ownerUid = uid.val();
+        const auto layer = sp<Layer>::make(args);
+        mFlinger.injectLegacyLayer(layer);
+        createRootLayerWithUid(sequence, uid);
+        return layer;
+    }
+
     auto destroyLayer(sp<Layer>& layer) {
         uint32_t sequence = static_cast<uint32_t>(layer->sequence);
         mFlinger.releaseLegacyLayer(sequence);
@@ -157,12 +182,13 @@
         ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
     }
 
-    std::shared_ptr<RefreshRateSelector> mSelector =
-            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
-                                                                              LO_FPS),
-                                                            createDisplayMode(DisplayModeId(1),
-                                                                              HI_FPS)),
-                                                  DisplayModeId(0));
+    std::shared_ptr<RefreshRateSelector> mSelector = std::make_shared<RefreshRateSelector>(
+            makeModes(createDisplayMode(DisplayModeId(0), LO_FPS),
+                      createDisplayMode(DisplayModeId(1), HI_FPS),
+                      createVrrDisplayMode(kVrrModeId, HI_FPS,
+                                           hal::VrrConfig{.minFrameIntervalNs =
+                                                                  HI_FPS.getPeriodNsecs()})),
+            DisplayModeId(0));
 
     mock::SchedulerCallback mSchedulerCallback;
     TestableSurfaceFlinger mFlinger;
@@ -214,6 +240,146 @@
     EXPECT_EQ(1u, activeLayerCount());
 }
 
+TEST_F(LayerHistoryIntegrationTest, oneLayer) {
+    createLegacyAndFrontedEndLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    // No layers returned if no layers are active.
+    EXPECT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    // Max returned if active layers have insufficient history.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+        setBuffer(1);
+        updateLayerSnapshotsAndLayerHistory(time);
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        time += LO_FPS_PERIOD;
+    }
+
+    // Max is returned since we have enough history but there is no timestamp votes.
+    for (size_t i = 0; i < 10; i++) {
+        setBuffer(1);
+        updateLayerSnapshotsAndLayerHistory(time);
+        ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1u, activeLayerCount());
+        time += LO_FPS_PERIOD;
+    }
+}
+
+TEST_F(LayerHistoryIntegrationTest, gameFrameRateOverrideMapping) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 60.0f}));
+
+    auto overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(0_Hz, overridePair.first);
+    EXPECT_EQ(60_Hz, overridePair.second);
+
+    history().updateGameModeFrameRateOverride(FrameRateOverride({0, 40.0f}));
+    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 120.0f}));
+
+    overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(40_Hz, overridePair.first);
+    EXPECT_EQ(60_Hz, overridePair.second);
+
+    overridePair = history().getGameFrameRateOverride(1);
+    EXPECT_EQ(120_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+
+    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 0.0f}));
+    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 0.0f}));
+
+    overridePair = history().getGameFrameRateOverride(0);
+    EXPECT_EQ(40_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+
+    overridePair = history().getGameFrameRateOverride(1);
+    EXPECT_EQ(0_Hz, overridePair.first);
+    EXPECT_EQ(0_Hz, overridePair.second);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerGameFrameRateOverride) {
+    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+    const uid_t uid = 0;
+    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+    const Fps gameModeFrameRate = Fps::fromValue(60.0f);
+
+    auto layer = createLegacyAndFrontedEndLayerWithUid(1, gui::Uid(uid));
+    showLayer(1);
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    // update game default frame rate override
+    history().updateGameDefaultFrameRateOverride(
+            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+    LayerHistory::Summary summary;
+    scheduler::LayerProps layerProps = {
+            .visible = true,
+            .bounds = {0, 0, 100, 100},
+            .transform = {},
+            .setFrameRateVote = {},
+            .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+            .isSmallDirty = false,
+            .isFrontBuffered = false,
+    };
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += gameDefaultFrameRate.getPeriodNsecs();
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(30.0_Hz, summary[0].desiredRefreshRate);
+
+    // test against setFrameRate vote
+    setFrameRate(1,
+                 Layer::FrameRate(Fps::fromValue(120.0f), Layer::FrameRateCompatibility::Default));
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    const Fps setFrameRate = Fps::fromValue(120.0f);
+    layerProps.setFrameRateVote =
+            Layer::FrameRate(setFrameRate, Layer::FrameRateCompatibility::Default);
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += setFrameRate.getPeriodNsecs();
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(120.0_Hz, summary[0].desiredRefreshRate);
+
+    // update game mode frame rate override
+    history().updateGameModeFrameRateOverride(
+            FrameRateOverride({uid, gameModeFrameRate.getValue()}));
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += gameModeFrameRate.getPeriodNsecs();
+        summary = summarizeLayerHistory(time);
+    }
+
+    ASSERT_EQ(1u, summary.size());
+    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+    ASSERT_EQ(60.0_Hz, summary[0].desiredRefreshRate);
+}
+
 TEST_F(LayerHistoryIntegrationTest, oneInvisibleLayer) {
     createLegacyAndFrontedEndLayer(1);
     nsecs_t time = systemTime();
@@ -238,6 +404,110 @@
     EXPECT_EQ(0u, activeLayerCount());
 }
 
+TEST_F(LayerHistoryIntegrationTest, explicitTimestamp) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(LO_FPS, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerNoVote) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerMinVote) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerMaxVote) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += LO_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became inactive
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_TRUE(summarizeLayerHistory(time).empty());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVote) {
     createLegacyAndFrontedEndLayer(1);
     setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
@@ -273,7 +543,338 @@
     EXPECT_EQ(1, frequentLayerCount(time));
 }
 
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitExactVote2) {
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became infrequent, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitGte_vrr) {
+    // Set the test to be on a vrr mode.
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    mSelector->setActiveMode(kVrrModeId, HI_FPS);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, 0);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became inactive, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitGte, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(33_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// Test for MRR device with VRR features enabled.
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitGte_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+    // The vrr_config flag is explicitly not set false because this test for an MRR device
+    // should still work in a VRR-capable world.
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (33_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_GTE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, 0);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithCategory_vrrFeatureOff) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (73.4_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+
+    // Set default to Min so it is obvious that the vote reset triggered.
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There is only 1 LayerRequirement due to the disabled flag frame_rate_category_mrr.
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
+// the category is NoPreference.
+TEST_F(LayerHistoryIntegrationTest, oneLayerCategoryNoPreference) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (0_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+
+    // layer became infrequent
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    EXPECT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (73.4_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There are 2 LayerRequirement's due to the frame rate category.
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the layer's category specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // Second LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote);
+    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithCategoryNotVisibleDoesNotVote) {
+    SET_FLAG_FOR_TEST(flags::misc1, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    hideLayer(1);
+    setFrameRate(1, (12.34_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // Layer is not visible, so the layer is moved to inactive, infrequent, and it will not have
+    // votes to consider for refresh rate selection.
+    ASSERT_EQ(0u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayer) {
+    SET_FLAG_FOR_TEST(flags::misc1, false);
+
+    auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (60_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, 0);
+
+    auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+    hideLayer(2);
+    setFrameRate(2, (90_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, 0);
+
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    setBuffer(1);
+    setBuffer(2);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(2u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(2u, activeLayerCount());
+    EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayerDoesNotVote) {
+    SET_FLAG_FOR_TEST(flags::misc1, true);
+
+    auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+    showLayer(1);
+    setFrameRate(1, (60_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, 0);
+
+    auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+    hideLayer(2);
+    setFrameRate(2, (90_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, 0);
+
+    nsecs_t time = systemTime();
+
+    // Post a buffer to the layers to make them active
+    setBuffer(1);
+    setBuffer(2);
+    updateLayerSnapshotsAndLayerHistory(time);
+
+    EXPECT_EQ(2u, layerCount());
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, frontBufferedLayerVotesMax) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setFrontBuffer(1);
+    showLayer(1);
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // layer is active but infrequent.
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setFrontBufferWithPresentTime(layer, time);
+        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+    }
+
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Layer still active due to front buffering, but it's infrequent.
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
 TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     createLegacyAndFrontedEndLayer(1);
     setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
 
@@ -291,6 +892,49 @@
     EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
 }
 
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVoteWithFixedSourceAndNoPreferenceCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, false);
+
+    auto layer = createLegacyAndFrontedEndLayer(1);
+    setFrameRate(1, (45.6_Hz).getValue(), ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+                 ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+    setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE);
+
+    EXPECT_EQ(1u, layerCount());
+    EXPECT_EQ(0u, activeLayerCount());
+
+    nsecs_t time = systemTime();
+    updateLayerSnapshotsAndLayerHistory(time);
+    for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+        setBufferWithPresentTime(layer, time);
+        time += HI_FPS_PERIOD;
+    }
+
+    // There are 2 LayerRequirement's due to the frame rate category.
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    // First LayerRequirement is the layer's category specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::NoPreference, summarizeLayerHistory(time)[0].frameRateCategory);
+
+    // Second LayerRequirement is the frame rate specification
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+              summarizeLayerHistory(time)[1].vote);
+    EXPECT_EQ(45.6_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
+
+    // layer became infrequent, but the vote stays
+    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+    ASSERT_EQ(2u, summarizeLayerHistory(time).size());
+    EXPECT_EQ(1u, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(FrameRateCategory::NoPreference, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
 TEST_F(LayerHistoryIntegrationTest, multipleLayers) {
     auto layer1 = createLegacyAndFrontedEndLayer(1);
     auto layer2 = createLegacyAndFrontedEndLayer(2);
@@ -803,7 +1447,15 @@
 
     // layer is active but infrequent.
     for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        auto props = layer->getLayerProps();
+        scheduler::LayerProps props = {
+                .visible = false,
+                .bounds = {0, 0, 100, 100},
+                .transform = {},
+                .setFrameRateVote = {},
+                .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+                .isSmallDirty = false,
+                .isFrontBuffered = false,
+        };
         if (i % 3 == 0) {
             props.isSmallDirty = false;
         } else {
@@ -836,8 +1488,15 @@
 
     // uiLayer is updating small dirty.
     for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
-        auto props = uiLayer->getLayerProps();
-        props.isSmallDirty = true;
+        scheduler::LayerProps props = {
+                .visible = false,
+                .bounds = {0, 0, 100, 100},
+                .transform = {},
+                .setFrameRateVote = {},
+                .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+                .isSmallDirty = true,
+                .isFrontBuffered = false,
+        };
         setBuffer(1);
         uiLayer->setDesiredPresentTime(0, false /*autotimestamp*/);
         updateLayerSnapshotsAndLayerHistory(time);
@@ -1001,8 +1660,6 @@
         mappings.push_back(std::make_pair(kAppId1, kThreshold1));
         mappings.push_back(std::make_pair(kAppId2, kThreshold2));
 
-        mFlinger.enableNewFrontEnd();
-
         mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
         mScheduler->updateSmallAreaDetection(mappings);
     }
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
deleted file mode 100644
index 9b8ff42..0000000
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ /dev/null
@@ -1,1451 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wextra"
-
-#undef LOG_TAG
-#define LOG_TAG "LayerHistoryTest"
-
-#include <Layer.h>
-#include <com_android_graphics_surfaceflinger_flags.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#include <common/test/FlagUtils.h>
-#include "FpsOps.h"
-#include "Scheduler/LayerHistory.h"
-#include "Scheduler/LayerInfo.h"
-#include "TestableScheduler.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockDisplayMode.h"
-#include "mock/MockLayer.h"
-#include "mock/MockSchedulerCallback.h"
-
-using testing::_;
-using testing::Return;
-using testing::ReturnRef;
-
-namespace android::scheduler {
-
-using MockLayer = android::mock::MockLayer;
-
-using android::mock::createDisplayMode;
-
-// WARNING: LEGACY TESTS FOR LEGACY FRONT END
-// Update LayerHistoryIntegrationTest instead
-class LayerHistoryTest : public testing::Test {
-protected:
-    static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
-    static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
-    static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
-    static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
-
-    static constexpr Fps LO_FPS = 30_Hz;
-    static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
-
-    static constexpr Fps HI_FPS = 90_Hz;
-    static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
-
-    LayerHistoryTest() { mFlinger.resetScheduler(mScheduler); }
-
-    LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
-    const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
-
-    LayerHistory::Summary summarizeLayerHistory(nsecs_t now) {
-        // LayerHistory::summarize makes no guarantee of the order of the elements in the summary
-        // however, for testing only, a stable order is required, therefore we sort the list here.
-        // Any tests requiring ordered results must create layers with names.
-        auto summary = history().summarize(*mScheduler->refreshRateSelector(), now);
-        std::sort(summary.begin(), summary.end(),
-                  [](const RefreshRateSelector::LayerRequirement& lhs,
-                     const RefreshRateSelector::LayerRequirement& rhs) -> bool {
-                      return lhs.name < rhs.name;
-                  });
-        return summary;
-    }
-
-    size_t layerCount() const { return mScheduler->layerHistorySize(); }
-    size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS {
-        return history().mActiveLayerInfos.size();
-    }
-
-    auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
-        const auto& infos = history().mActiveLayerInfos;
-        return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
-            return pair.second.second->isFrequent(now).isFrequent;
-        });
-    }
-
-    auto animatingLayerCount(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->isAnimating(now);
-        });
-    }
-
-    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());
-        if (found != LayerHistory::LayerStatus::NotFound) {
-            layerPair->second->setDefaultLayerVote(vote);
-        }
-    }
-
-    auto createLayer() { return sp<MockLayer>::make(mFlinger.flinger()); }
-    auto createLayer(std::string name) {
-        return sp<MockLayer>::make(mFlinger.flinger(), std::move(name));
-    }
-    auto createLayer(std::string name, uint32_t uid) {
-        return sp<MockLayer>::make(mFlinger.flinger(), std::move(name), std::move(uid));
-    }
-
-    void recordFramesAndExpect(const sp<MockLayer>& layer, nsecs_t& time, Fps frameRate,
-                               Fps desiredRefreshRate, int numFrames) {
-        LayerHistory::Summary summary;
-        for (int i = 0; i < numFrames; i++) {
-            history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                             LayerHistory::LayerUpdateType::Buffer);
-            time += frameRate.getPeriodNsecs();
-
-            summary = summarizeLayerHistory(time);
-        }
-
-        ASSERT_EQ(1, summary.size());
-        ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-        ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
-    }
-
-    std::shared_ptr<RefreshRateSelector> mSelector =
-            std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
-                                                                              LO_FPS),
-                                                            createDisplayMode(DisplayModeId(1),
-                                                                              HI_FPS)),
-                                                  DisplayModeId(0));
-
-    mock::SchedulerCallback mSchedulerCallback;
-    TestableSurfaceFlinger mFlinger;
-    TestableScheduler* mScheduler = new TestableScheduler(mSelector, mFlinger, mSchedulerCallback);
-};
-
-namespace {
-
-using namespace com::android::graphics::surfaceflinger;
-
-TEST_F(LayerHistoryTest, singleLayerNoVoteDefaultCompatibility) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-    EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
-            .WillOnce(Return(FrameRateCompatibility::NoVote));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-
-    // No layers returned if no layers are active.
-    EXPECT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-
-    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    history().setDefaultFrameRateCompatibility(layer->getSequence(),
-
-                                               layer->getDefaultFrameRateCompatibility(),
-                                               true /* contentDetectionEnabled */);
-
-    EXPECT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(1, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTest, singleLayerMinVoteDefaultCompatibility) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-    EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
-            .WillOnce(Return(FrameRateCompatibility::Min));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-
-    EXPECT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-
-    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    history().setDefaultFrameRateCompatibility(layer->getSequence(),
-                                               layer->getDefaultFrameRateCompatibility(),
-                                               true /* contentDetectionEnabled */);
-
-    auto summary = summarizeLayerHistory(time);
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTest, oneLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    // history().registerLayer(layer, LayerHistory::LayerVoteType::Max);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-
-    // No layers returned if no layers are active.
-    EXPECT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-
-    // Max returned if active layers have insufficient history.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_EQ(1, summarizeLayerHistory(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        time += LO_FPS_PERIOD;
-    }
-
-    // Max is returned since we have enough history but there is no timestamp votes.
-    for (int i = 0; i < 10; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        ASSERT_EQ(1, summarizeLayerHistory(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        time += LO_FPS_PERIOD;
-    }
-}
-
-TEST_F(LayerHistoryTest, gameFrameRateOverrideMapping) {
-    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
-
-    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 60.0f}));
-
-    auto overridePair = history().getGameFrameRateOverride(0);
-    EXPECT_EQ(0_Hz, overridePair.first);
-    EXPECT_EQ(60_Hz, overridePair.second);
-
-    history().updateGameModeFrameRateOverride(FrameRateOverride({0, 40.0f}));
-    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 120.0f}));
-
-    overridePair = history().getGameFrameRateOverride(0);
-    EXPECT_EQ(40_Hz, overridePair.first);
-    EXPECT_EQ(60_Hz, overridePair.second);
-
-    overridePair = history().getGameFrameRateOverride(1);
-    EXPECT_EQ(120_Hz, overridePair.first);
-    EXPECT_EQ(0_Hz, overridePair.second);
-
-    history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 0.0f}));
-    history().updateGameModeFrameRateOverride(FrameRateOverride({1, 0.0f}));
-
-    overridePair = history().getGameFrameRateOverride(0);
-    EXPECT_EQ(40_Hz, overridePair.first);
-    EXPECT_EQ(0_Hz, overridePair.second);
-
-    overridePair = history().getGameFrameRateOverride(1);
-    EXPECT_EQ(0_Hz, overridePair.first);
-    EXPECT_EQ(0_Hz, overridePair.second);
-}
-
-TEST_F(LayerHistoryTest, oneLayerGameFrameRateOverride) {
-    SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
-
-    const uid_t uid = 0;
-    const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
-    const Fps gameModeFrameRate = Fps::fromValue(60.0f);
-    const auto layer = createLayer("GameFrameRateLayer", uid);
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-    EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    // update game default frame rate override
-    history().updateGameDefaultFrameRateOverride(
-            FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
-
-    nsecs_t time = systemTime();
-    LayerHistory::Summary summary;
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += gameDefaultFrameRate.getPeriodNsecs();
-
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
-    ASSERT_EQ(30.0_Hz, summary[0].desiredRefreshRate);
-
-    // test against setFrameRate vote
-    const Fps setFrameRate = Fps::fromValue(120.0f);
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(setFrameRate, Layer::FrameRateCompatibility::Default)));
-
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += setFrameRate.getPeriodNsecs();
-
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
-    ASSERT_EQ(120.0_Hz, summary[0].desiredRefreshRate);
-
-    // update game mode frame rate override
-    history().updateGameModeFrameRateOverride(
-            FrameRateOverride({uid, gameModeFrameRate.getValue()}));
-
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += gameModeFrameRate.getPeriodNsecs();
-
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
-    ASSERT_EQ(60.0_Hz, summary[0].desiredRefreshRate);
-}
-
-TEST_F(LayerHistoryTest, oneInvisibleLayer) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-
-    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    auto summary = summarizeLayerHistory(time);
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    // Layer is still considered inactive so we expect to get Min
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-    history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-
-    summary = summarizeLayerHistory(time);
-    EXPECT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-}
-
-TEST_F(LayerHistoryTest, explicitTimestamp) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(LO_FPS, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerNoVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::NoVote);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerMinVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Min);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerMaxVote) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Max);
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_TRUE(summarizeLayerHistory(time).empty());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitVote) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
-                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    // First LayerRequirement is the frame rate specification
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
-}
-
-// This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
-// the category is NoPreference.
-TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
-                                                    Seamlessness::OnlySeamless,
-                                                    FrameRateCategory::NoPreference)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    EXPECT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    EXPECT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
-                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    // There are 2 LayerRequirement's due to the frame rate category.
-    ASSERT_EQ(2, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    // First LayerRequirement is the layer's category specification
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
-
-    // Second LayerRequirement is the frame rate specification
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote);
-    EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::Default, summarizeLayerHistory(time)[1].frameRateCategory);
-
-    // layer became inactive, but the vote stays
-    setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(2, summarizeLayerHistory(time).size());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
-}
-
-TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategoryNotVisibleDoesNotVote) {
-    SET_FLAG_FOR_TEST(flags::misc1, true);
-
-    auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(12.34_Hz, Layer::FrameRateCompatibility::Default,
-                                            Seamlessness::OnlySeamless, FrameRateCategory::High)));
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-
-    nsecs_t time = systemTime();
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-    }
-
-    // Layer is not visible, so the layer is moved to inactive, infrequent, and it will not have
-    // votes to consider for refresh rate selection.
-    ASSERT_EQ(0, summarizeLayerHistory(time).size());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, multipleLayers) {
-    auto layer1 = createLayer("A");
-    auto layer2 = createLayer("B");
-    auto layer3 = createLayer("C");
-
-    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer2, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_CALL(*layer3, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer3, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(3, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    LayerHistory::Summary summary;
-
-    // layer1 is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // layer2 is frequent and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    // layer1 is still active but infrequent.
-    history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
-    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_EQ(HI_FPS, summarizeLayerHistory(time)[1].desiredRefreshRate);
-
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer1 is no longer active.
-    // layer2 is frequent and has low refresh rate.
-    for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer2 still has low refresh rate.
-    // layer3 has high refresh rate but not enough history.
-    constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
-        if (i % RATIO == 0) {
-            history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
-                             LayerHistory::LayerUpdateType::Buffer);
-        }
-
-        history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer3 becomes recently active.
-    history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    summary = summarizeLayerHistory(time);
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer1 expires.
-    layer1.clear();
-    summary = summarizeLayerHistory(time);
-    ASSERT_EQ(2, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
-    EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
-    EXPECT_EQ(2, layerCount());
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-
-    // layer2 still has low refresh rate.
-    // layer3 becomes inactive.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += LO_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer2 expires.
-    layer2.clear();
-    summary = summarizeLayerHistory(time);
-    EXPECT_TRUE(summary.empty());
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // layer3 becomes active and has high refresh rate.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
-        history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_EQ(HI_FPS, summary[0].desiredRefreshRate);
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-
-    // layer3 expires.
-    layer3.clear();
-    summary = summarizeLayerHistory(time);
-    EXPECT_TRUE(summary.empty());
-    EXPECT_EQ(0, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, inactiveLayers) {
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    // the very first updates makes the layer frequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-        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));
-    }
-
-    // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
-    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    EXPECT_EQ(1, layerCount());
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    // advance the time for the previous frame to be inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-
-    // Now even if we post a quick few frame we should stay infrequent
-    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-
-        EXPECT_EQ(1, layerCount());
-        ASSERT_EQ(1, summarizeLayerHistory(time).size());
-        EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-        EXPECT_EQ(1, activeLayerCount());
-        EXPECT_EQ(0, frequentLayerCount(time));
-    }
-
-    // More quick frames will get us to frequent again
-    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    time += HI_FPS_PERIOD;
-
-    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));
-}
-
-TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
-    SET_FLAG_FOR_TEST(flags::misc1, false);
-
-    auto explicitVisiblelayer = createLayer();
-    auto explicitInvisiblelayer = createLayer();
-
-    EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(60_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(90_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    nsecs_t time = systemTime();
-
-    // Post a buffer to the layers to make them active
-    history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
-                     time, time, LayerHistory::LayerUpdateType::Buffer);
-    history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
-                     time, time, LayerHistory::LayerUpdateType::Buffer);
-
-    EXPECT_EQ(2, layerCount());
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(2, activeLayerCount());
-    EXPECT_EQ(2, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, invisibleExplicitLayerDoesNotVote) {
-    SET_FLAG_FOR_TEST(flags::misc1, true);
-
-    auto explicitVisiblelayer = createLayer();
-    auto explicitInvisiblelayer = createLayer();
-
-    EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(60_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
-    EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(
-                    Layer::FrameRate(90_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
-
-    nsecs_t time = systemTime();
-
-    // Post a buffer to the layers to make them active
-    history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
-                     time, time, LayerHistory::LayerUpdateType::Buffer);
-    history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
-                     time, time, LayerHistory::LayerUpdateType::Buffer);
-
-    EXPECT_EQ(2, layerCount());
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
-              summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // layer is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // another update with the same cadence keep in infrequent
-    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::Buffer);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // an update as animation will immediately vote for Max
-    history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                     LayerHistory::LayerUpdateType::AnimationTX);
-    time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(1, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, frontBufferedLayerVotesMax) {
-    SET_FLAG_FOR_TEST(flags::vrr_config, true);
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-    EXPECT_CALL(*layer, isFrontBuffered()).WillRepeatedly(Return(true));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // layer is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-    }
-
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // layer became inactive
-    time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
-    ASSERT_EQ(1, summarizeLayerHistory(time).size());
-    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) {
-    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 a buffer after long inactivity should retain the layer as active
-    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);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // posting more infrequent buffer should make the layer infrequent
-    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).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::Min, summarizeLayerHistory(time)[0].vote);
-    EXPECT_EQ(1, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // 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());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // posting more buffers would mean starting of an animation, so making the layer frequent
-    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());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // posting a buffer after long inactivity should retain the layer as active
-    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());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // posting another buffer should keep the layer frequent
-    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());
-    EXPECT_EQ(1, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-}
-
-TEST_F(LayerHistoryTest, getFramerate) {
-    auto layer = createLayer();
-
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(1, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-    EXPECT_EQ(0, animatingLayerCount(time));
-
-    // layer is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        history().record(layer->getSequence(), layer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
-    }
-
-    float expectedFramerate = 1e9f / MAX_FREQUENT_LAYER_PERIOD_NS.count();
-    EXPECT_FLOAT_EQ(expectedFramerate, history().getLayerFramerate(time, layer->getSequence()));
-}
-
-TEST_F(LayerHistoryTest, heuristicLayer60Hz) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-    for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
-        recordFramesAndExpect(layer, time, Fps::fromValue(fps), 60_Hz, PRESENT_TIME_HISTORY_SIZE);
-    }
-}
-
-TEST_F(LayerHistoryTest, heuristicLayer60_30Hz) {
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
-
-    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 30_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
-    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, false);
-
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTest, heuristicLayerNotOscillating_useKnownRefreshRate) {
-    SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, true);
-
-    const auto layer = createLayer();
-    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    nsecs_t time = systemTime();
-
-    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 27.1_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
-    recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
-}
-
-TEST_F(LayerHistoryTest, smallDirtyLayer) {
-    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));
-
-    LayerHistory::Summary summary;
-
-    // layer is active but infrequent.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
-        auto props = layer->getLayerProps();
-        if (i % 3 == 0) {
-            props.isSmallDirty = false;
-        } else {
-            props.isSmallDirty = true;
-        }
-
-        history().record(layer->getSequence(), props, time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
-    EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate);
-}
-
-TEST_F(LayerHistoryTest, smallDirtyInMultiLayer) {
-    auto layer1 = createLayer("UI");
-    auto layer2 = createLayer("Video");
-
-    EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
-
-    EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*layer2, getFrameRateForLayerTree())
-            .WillRepeatedly(
-                    Return(Layer::FrameRate(30_Hz, Layer::FrameRateCompatibility::Default)));
-
-    nsecs_t time = systemTime();
-
-    EXPECT_EQ(2, layerCount());
-    EXPECT_EQ(0, activeLayerCount());
-    EXPECT_EQ(0, frequentLayerCount(time));
-
-    LayerHistory::Summary summary;
-
-    // layer1 is updating small dirty.
-    for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
-        auto props = layer1->getLayerProps();
-        props.isSmallDirty = true;
-        history().record(layer1->getSequence(), props, 0 /*presentTime*/, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-        time += HI_FPS_PERIOD;
-        summary = summarizeLayerHistory(time);
-    }
-
-    ASSERT_EQ(1, summary.size());
-    ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
-    ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
-}
-
-class LayerHistoryTestParameterized : public LayerHistoryTest,
-                                      public testing::WithParamInterface<std::chrono::nanoseconds> {
-};
-
-TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
-    std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
-    auto heuristicLayer = createLayer("HeuristicLayer");
-
-    EXPECT_CALL(*heuristicLayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*heuristicLayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate()));
-
-    auto infrequentLayer = createLayer("InfrequentLayer");
-    EXPECT_CALL(*infrequentLayer, isVisible()).WillRepeatedly(Return(true));
-    EXPECT_CALL(*infrequentLayer, getFrameRateForLayerTree())
-            .WillRepeatedly(Return(Layer::FrameRate()));
-
-    const nsecs_t startTime = systemTime();
-
-    const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
-    history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
-                     startTime, LayerHistory::LayerUpdateType::Buffer);
-    history().record(infrequentLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
-                     startTime, LayerHistory::LayerUpdateType::Buffer);
-
-    nsecs_t time = startTime;
-    nsecs_t lastInfrequentUpdate = startTime;
-    const int totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
-    int infrequentLayerUpdates = 0;
-    while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
-        time += heuristicUpdateDelta.count();
-        history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), time, time,
-                         LayerHistory::LayerUpdateType::Buffer);
-
-        if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
-            ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
-                  totalInfrequentLayerUpdates);
-            lastInfrequentUpdate = time;
-            history().record(infrequentLayer->getSequence(), infrequentLayer->getLayerProps(), time,
-                             time, LayerHistory::LayerUpdateType::Buffer);
-            infrequentLayerUpdates++;
-        }
-
-        if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
-            ASSERT_NE(0, summarizeLayerHistory(time).size());
-            ASSERT_GE(2, summarizeLayerHistory(time).size());
-
-            bool max = false;
-            bool min = false;
-            Fps heuristic;
-            for (const auto& layer : summarizeLayerHistory(time)) {
-                if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
-                    heuristic = layer.desiredRefreshRate;
-                } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
-                    max = true;
-                } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
-                    min = true;
-                }
-            }
-
-            if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
-                EXPECT_EQ(24_Hz, heuristic);
-                EXPECT_FALSE(max);
-                if (summarizeLayerHistory(time).size() == 2) {
-                    EXPECT_TRUE(min);
-                }
-            }
-        }
-    }
-}
-
-INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryTestParameterized,
-                        ::testing::Values(1s, 2s, 3s, 4s, 5s));
-
-} // namespace
-} // namespace android::scheduler
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index aecfcba..c7cc21c 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -61,18 +61,6 @@
 
 class LayerLifecycleManagerTest : public LayerHierarchyTestBase {
 protected:
-    std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
-        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/true,
-                                                                /*parent=*/UNASSIGNED_LAYER_ID,
-                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
-    }
-
-    std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
-        return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
-                                                                parentId,
-                                                                /*mirror=*/UNASSIGNED_LAYER_ID));
-    }
-
     RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
                                                 uint32_t layerId) {
         return lifecycleManager.getLayerFromId(layerId);
@@ -461,8 +449,8 @@
                                                                HAL_PIXEL_FORMAT_RGBA_8888,
                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
     EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
-              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
-                                                       RequestedLayerState::Changes::Content)
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content)
                       .get());
     mLifecycleManager.commitChanges();
 
@@ -493,10 +481,10 @@
                                                                HAL_PIXEL_FORMAT_RGB_888,
                                                                GRALLOC_USAGE_PROTECTED /*usage*/));
     EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
-              ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
-                                                       RequestedLayerState::Changes::Content |
-                                                       RequestedLayerState::Changes::VisibleRegion |
-                                                       RequestedLayerState::Changes::Visibility)
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Visibility)
                       .get());
     mLifecycleManager.commitChanges();
 }
@@ -538,7 +526,8 @@
               ftl::Flags<RequestedLayerState::Changes>(
                       RequestedLayerState::Changes::Content |
                       RequestedLayerState::Changes::AffectsChildren |
-                      RequestedLayerState::Changes::VisibleRegion)
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Input)
                       .string());
     EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(startingAlpha));
     mLifecycleManager.commitChanges();
@@ -551,7 +540,8 @@
               ftl::Flags<RequestedLayerState::Changes>(
                       RequestedLayerState::Changes::Content |
                       RequestedLayerState::Changes::AffectsChildren |
-                      RequestedLayerState::Changes::VisibleRegion)
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::Input)
                       .string());
     EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(endingAlpha));
     mLifecycleManager.commitChanges();
@@ -560,4 +550,83 @@
               ftl::Flags<RequestedLayerState::Changes>().string());
 }
 
+TEST_F(LayerLifecycleManagerTest, layerSecureChangesSetsVisibilityChangeFlag) {
+    // add a default buffer and make the layer secure
+    setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+    setBuffer(1,
+              std::make_shared<renderengine::mock::
+                                       FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                            1ULL /* bufferId */,
+                                                            HAL_PIXEL_FORMAT_RGBA_8888,
+                                                            GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+
+    mLifecycleManager.commitChanges();
+
+    // set new buffer but layer secure doesn't change
+    setBuffer(1,
+              std::make_shared<renderengine::mock::
+                                       FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+                                                            2ULL /* bufferId */,
+                                                            HAL_PIXEL_FORMAT_RGBA_8888,
+                                                            GRALLOC_USAGE_SW_READ_NEVER /*usage*/));
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+              ftl::Flags<RequestedLayerState::Changes>(
+                      RequestedLayerState::Changes::Buffer | RequestedLayerState::Changes::Content)
+                      .get());
+    mLifecycleManager.commitChanges();
+
+    // change layer flags and confirm visibility flag is set
+    setFlags(1, layer_state_t::eLayerSecure, 0);
+    EXPECT_TRUE(
+            mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+    mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, isSimpleBufferUpdate) {
+    auto layer = rootLayer(1);
+
+    // no buffer changes
+    EXPECT_FALSE(layer->isSimpleBufferUpdate({}));
+
+    {
+        layer_state_t state;
+        state.what = layer_state_t::eBufferChanged;
+        EXPECT_TRUE(layer->isSimpleBufferUpdate(state));
+    }
+
+    {
+        layer_state_t state;
+        state.what = layer_state_t::eReparent | layer_state_t::eBufferChanged;
+        EXPECT_FALSE(layer->isSimpleBufferUpdate(state));
+    }
+
+    {
+        layer_state_t state;
+        state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged;
+        state.x = 9;
+        state.y = 10;
+        EXPECT_FALSE(layer->isSimpleBufferUpdate(state));
+    }
+
+    {
+        layer->x = 9;
+        layer->y = 10;
+        layer_state_t state;
+        state.what = layer_state_t::ePositionChanged | layer_state_t::eBufferChanged;
+        state.x = 9;
+        state.y = 10;
+        EXPECT_TRUE(layer->isSimpleBufferUpdate(state));
+    }
+}
+
+TEST_F(LayerLifecycleManagerTest, testInputInfoOfRequestedLayerState) {
+    // By default the layer has no buffer, so it doesn't need an input info
+    EXPECT_FALSE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
+
+    setBuffer(111);
+    mLifecycleManager.commitChanges();
+
+    EXPECT_TRUE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 3baa48d..75d2fa3 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -17,6 +17,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <common/test/FlagUtils.h>
 #include <renderengine/mock/FakeExternalTexture.h>
 
 #include "FrontEnd/LayerHierarchy.h"
@@ -26,6 +27,9 @@
 #include "LayerHierarchyTest.h"
 #include "ui/GraphicTypes.h"
 
+#include <com_android_graphics_libgui_flags.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 #define UPDATE_AND_VERIFY(BUILDER, ...)                                    \
     ({                                                                     \
         SCOPED_TRACE("");                                                  \
@@ -42,6 +46,7 @@
 
 using ftl::Flags;
 using namespace ftl::flag_operators;
+using namespace com::android::graphics::surfaceflinger;
 
 // To run test:
 /**
@@ -52,6 +57,17 @@
 
 class LayerSnapshotTest : public LayerSnapshotTestBase {
 protected:
+    const Layer::FrameRate FRAME_RATE_VOTE1 =
+            Layer::FrameRate(67_Hz, scheduler::FrameRateCompatibility::Default);
+    const Layer::FrameRate FRAME_RATE_VOTE2 =
+            Layer::FrameRate(14_Hz, scheduler::FrameRateCompatibility::Default);
+    const Layer::FrameRate FRAME_RATE_VOTE3 =
+            Layer::FrameRate(99_Hz, scheduler::FrameRateCompatibility::Default);
+    const Layer::FrameRate FRAME_RATE_TREE =
+            Layer::FrameRate(Fps(), scheduler::FrameRateCompatibility::NoVote);
+    const Layer::FrameRate FRAME_RATE_NO_VOTE =
+            Layer::FrameRate(Fps(), scheduler::FrameRateCompatibility::Default);
+
     LayerSnapshotTest() : LayerSnapshotTestBase() {
         UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
     }
@@ -209,6 +225,17 @@
     UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
 }
 
+TEST_F(LayerSnapshotTest, offscreenLayerSnapshotIsInvisible) {
+    EXPECT_EQ(getSnapshot(111)->isVisible, true);
+
+    reparentLayer(11, UNASSIGNED_LAYER_ID);
+    destroyLayerHandle(11);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+    EXPECT_EQ(getSnapshot(111)->isVisible, false);
+    EXPECT_TRUE(getSnapshot(111)->changes.test(RequestedLayerState::Changes::Visibility));
+}
+
 // relative tests
 TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) {
     reparentRelativeLayer(13, 11);
@@ -250,7 +277,8 @@
 TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) {
     setColor(11, {1._hf, 0._hf, 0._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot(11)->changes, RequestedLayerState::Changes::Content);
+    EXPECT_EQ(getSnapshot(11)->changes,
+              RequestedLayerState::Changes::Content);
     EXPECT_EQ(getSnapshot(11)->clientChanges, layer_state_t::eColorChanged);
     EXPECT_EQ(getSnapshot(1)->changes.get(), 0u);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -260,25 +288,137 @@
 TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) {
     setColor(1, {1._hf, 0._hf, 0._hf});
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
-    EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content);
+    EXPECT_EQ(getSnapshot(1)->changes,
+              RequestedLayerState::Changes::Content);
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eColorChanged);
 }
 
-TEST_F(LayerSnapshotTest, GameMode) {
+TEST_F(LayerSnapshotTest, ChildrenInheritGameMode) {
+    setGameMode(1, gui::GameMode::Performance);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->gameMode, gui::GameMode::Performance);
+    EXPECT_EQ(getSnapshot(11)->gameMode, gui::GameMode::Performance);
+}
+
+TEST_F(LayerSnapshotTest, ChildrenCanOverrideGameMode) {
+    setGameMode(1, gui::GameMode::Performance);
+    setGameMode(11, gui::GameMode::Battery);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->gameMode, gui::GameMode::Performance);
+    EXPECT_EQ(getSnapshot(11)->gameMode, gui::GameMode::Battery);
+}
+
+TEST_F(LayerSnapshotTest, ReparentingUpdatesGameMode) {
+    setGameMode(1, gui::GameMode::Performance);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::GameMode | RequestedLayerState::Changes::Metadata);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+    EXPECT_EQ(getSnapshot(1)->gameMode, gui::GameMode::Performance);
+    EXPECT_EQ(getSnapshot(2)->gameMode, gui::GameMode::Unsupported);
+
+    reparentLayer(2, 1);
+    setZ(2, 2);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(2)->gameMode, gui::GameMode::Performance);
+}
+
+TEST_F(LayerSnapshotTest, UpdateMetadata) {
     std::vector<TransactionState> transactions;
     transactions.emplace_back();
     transactions.back().states.push_back({});
     transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
     transactions.back().states.front().state.metadata = LayerMetadata();
-    transactions.back().states.front().state.metadata.setInt32(METADATA_GAME_MODE, 42);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
     transactions.back().states.front().layerId = 1;
     transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
     mLifecycleManager.applyTransactions(transactions);
-    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode);
-    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::Metadata);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
     EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
-    EXPECT_EQ(static_cast<int32_t>(getSnapshot(1)->gameMode), 42);
-    EXPECT_EQ(static_cast<int32_t>(getSnapshot(11)->gameMode), 42);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
+}
+
+TEST_F(LayerSnapshotTest, UpdateMetadataOfHiddenLayers) {
+    hideLayer(1);
+
+    std::vector<TransactionState> transactions;
+    transactions.emplace_back();
+    transactions.back().states.push_back({});
+    transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+    // This test focuses on metadata used by ARC++ to ensure LayerMetadata is updated correctly,
+    // and not using stale data.
+    transactions.back().states.front().state.metadata = LayerMetadata();
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_UID, 123);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_WINDOW_TYPE, 234);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_TASK_ID, 345);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_MOUSE_CURSOR, 456);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_ACCESSIBILITY_ID, 567);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_OWNER_PID, 678);
+    transactions.back().states.front().state.metadata.setInt32(METADATA_CALLING_UID, 789);
+
+    transactions.back().states.front().layerId = 1;
+    transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+
+    mLifecycleManager.applyTransactions(transactions);
+    EXPECT_EQ(mLifecycleManager.getGlobalChanges(),
+              RequestedLayerState::Changes::Metadata | RequestedLayerState::Changes::Visibility |
+                      RequestedLayerState::Changes::VisibleRegion |
+                      RequestedLayerState::Changes::AffectsChildren);
+
+    // Setting includeMetadata=true to ensure metadata update is applied to LayerSnapshot
+    LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+                                    .layerLifecycleManager = mLifecycleManager,
+                                    .includeMetadata = true,
+                                    .displays = mFrontEndDisplayInfos,
+                                    .globalShadowSettings = globalShadowSettings,
+                                    .supportsBlur = true,
+                                    .supportedLayerGenericMetadata = {},
+                                    .genericLayerMetadataKeyMap = {}};
+    update(mSnapshotBuilder, args);
+
+    EXPECT_EQ(static_cast<int64_t>(getSnapshot(1)->clientChanges),
+              layer_state_t::eMetadataChanged | layer_state_t::eFlagsChanged);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_UID, -1), 123);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_WINDOW_TYPE, -1), 234);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_TASK_ID, -1), 345);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_MOUSE_CURSOR, -1), 456);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_ACCESSIBILITY_ID, -1), 567);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_OWNER_PID, -1), 678);
+    EXPECT_EQ(getSnapshot(1)->layerMetadata.getInt32(METADATA_CALLING_UID, -1), 789);
 }
 
 TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) {
@@ -659,6 +799,155 @@
               scheduler::FrameRateCompatibility::Default);
 }
 
+TEST_F(LayerSnapshotTest, frameRateSetAndGet) {
+    setFrameRate(1, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    // verify parent is gets no vote
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetParent) {
+    setFrameRate(111, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(111, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetParentAllVote) {
+    setFrameRate(1, FRAME_RATE_VOTE3.vote.rate.getValue(), 0, 0);
+    setFrameRate(11, FRAME_RATE_VOTE2.vote.rate.getValue(), 0, 0);
+    setFrameRate(111, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE3);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE2);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(111, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE3);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE2);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE2);
+
+    setFrameRate(11, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE3);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE3);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE3);
+
+    setFrameRate(1, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetChild) {
+    setFrameRate(1, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(1, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetChildAllVote) {
+    setFrameRate(1, FRAME_RATE_VOTE3.vote.rate.getValue(), 0, 0);
+    setFrameRate(11, FRAME_RATE_VOTE2.vote.rate.getValue(), 0, 0);
+    setFrameRate(111, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE3);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE2);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(1, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE2);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(11, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(111, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetChildAddAfterVote) {
+    setFrameRate(1, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    reparentLayer(111, 2);
+    std::vector<uint32_t> traversalOrder = {1, 11, 12, 121, 122, 1221, 13, 2, 111};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, traversalOrder);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+
+    reparentLayer(111, 11);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    setFrameRate(1, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSetAndGetChildRemoveAfterVote) {
+    setFrameRate(1, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_VOTE1);
+
+    reparentLayer(111, 2);
+    std::vector<uint32_t> traversalOrder = {1, 11, 12, 121, 122, 1221, 13, 2, 111};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, traversalOrder);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+
+    setFrameRate(1, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, traversalOrder);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 111})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
+TEST_F(LayerSnapshotTest, frameRateAddChildForParentWithTreeVote) {
+    setFrameRate(11, FRAME_RATE_VOTE1.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_TREE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_VOTE1);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate, FRAME_RATE_NO_VOTE);
+
+    setFrameRate(11, FRAME_RATE_NO_VOTE.vote.rate.getValue(), 0, 0);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 11})->frameRate, FRAME_RATE_NO_VOTE);
+    EXPECT_EQ(getSnapshot({.id = 12})->frameRate, FRAME_RATE_NO_VOTE);
+}
+
 TEST_F(LayerSnapshotTest, translateDataspace) {
     setDataspace(1, ui::Dataspace::UNKNOWN);
     UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
@@ -668,6 +957,8 @@
 // This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
 // interaction also works correctly with the setFrameRate API within SF frontend.
 TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11 (frame rate set to 244.f)
@@ -835,7 +1126,7 @@
     EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
     EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
               scheduler::FrameRateCompatibility::NoVote);
-    EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
     EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
@@ -864,6 +1155,8 @@
 }
 
 TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+    SET_FLAG_FOR_TEST(flags::frame_rate_category_mrr, true);
+
     // ROOT
     // ├── 1
     // │   ├── 11
@@ -924,7 +1217,7 @@
     EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
               scheduler::FrameRateCompatibility::NoVote);
     EXPECT_EQ(getSnapshot({.id = 1})->frameRate.category, FrameRateCategory::Default);
-    EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+    EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
 
     // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
     EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
@@ -1142,7 +1435,7 @@
 
 TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
     hideLayer(1);
-    setTrustedOverlay(1, true);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
     Region touch{Rect{0, 0, 1000, 1000}};
     setTouchableRegion(1, touch);
 
@@ -1151,6 +1444,16 @@
             gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
 }
 
+TEST_F(LayerSnapshotTest, alphaChangesPropagateToInput) {
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    setAlpha(1, 0.5f);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot(1)->inputInfo.alpha, 0.5f);
+}
+
 TEST_F(LayerSnapshotTest, isFrontBuffered) {
     setBuffer(1,
               std::make_shared<renderengine::mock::FakeExternalTexture>(
@@ -1190,6 +1493,42 @@
     EXPECT_TRUE(getSnapshot(11)->isSecure);
 }
 
+TEST_F(LayerSnapshotTest, setSensitiveForTracingConfigForSecureLayers) {
+    setFlags(11, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+}
+
+TEST_F(LayerSnapshotTest, setSensitiveForTracingFromInputWindowHandle) {
+    setInputInfo(11, [](auto& inputInfo) {
+        inputInfo.inputConfig |= gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY;
+    });
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(12)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+    EXPECT_FALSE(getSnapshot(2)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::SENSITIVE_FOR_PRIVACY));
+}
+
 // b/314350323
 TEST_F(LayerSnapshotTest, propagateDropInputMode) {
     setDropInputMode(1, gui::DropInputMode::ALL);
@@ -1237,6 +1576,17 @@
     EXPECT_TRUE(foundInputLayer);
 }
 
+TEST_F(LayerSnapshotTest, ForEachSnapshotsWithPredicate) {
+    std::vector<uint32_t> visitedUniqueSequences;
+    mSnapshotBuilder.forEachSnapshot(
+            [&](const std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+                visitedUniqueSequences.push_back(snapshot->uniqueSequence);
+            },
+            [](const frontend::LayerSnapshot& snapshot) { return snapshot.uniqueSequence == 111; });
+    EXPECT_EQ(visitedUniqueSequences.size(), 1u);
+    EXPECT_EQ(visitedUniqueSequences[0], 111u);
+}
+
 TEST_F(LayerSnapshotTest, canOccludePresentation) {
     setFlags(12, layer_state_t::eCanOccludePresentation, layer_state_t::eCanOccludePresentation);
     LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
@@ -1258,4 +1608,331 @@
     EXPECT_EQ(getSnapshot(1221)->inputInfo.canOccludePresentation, true);
 }
 
+TEST_F(LayerSnapshotTest, mirroredHierarchyIgnoresLocalTransform) {
+    SET_FLAG_FOR_TEST(flags::detached_mirror, true);
+    reparentLayer(12, UNASSIGNED_LAYER_ID);
+    setPosition(11, 2, 20);
+    setPosition(111, 20, 200);
+    mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+    std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+    UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+    // mirror root has no position set
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.tx(), 0);
+    EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->localTransform.ty(), 0);
+    // original root still has a position
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.tx(), 2);
+    EXPECT_EQ(getSnapshot({.id = 11})->localTransform.ty(), 20);
+
+    // mirror child still has the correct position
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 14u})->geomLayerTransform.ty(), 200);
+
+    // original child still has the correct position including its parent's position
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.tx(), 20);
+    EXPECT_EQ(getSnapshot({.id = 111})->localTransform.ty(), 200);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.tx(), 22);
+    EXPECT_EQ(getSnapshot({.id = 111})->geomLayerTransform.ty(), 220);
+}
+
+TEST_F(LayerSnapshotTest, overrideParentTrustedOverlayState) {
+    SET_FLAG_FOR_TEST(flags::override_trusted_overlay, true);
+    hideLayer(1);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
+
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    setTouchableRegion(11, touch);
+    setTouchableRegion(111, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // disable trusted overlay and override parent state
+    setTrustedOverlay(11, gui::TrustedOverlay::DISABLED);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_FALSE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_FALSE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // unset state and go back to default behavior of inheriting
+    // state
+    setTrustedOverlay(11, gui::TrustedOverlay::UNSET);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
+TEST_F(LayerSnapshotTest, doNotOverrideParentTrustedOverlayState) {
+    SET_FLAG_FOR_TEST(flags::override_trusted_overlay, false);
+    hideLayer(1);
+    setTrustedOverlay(1, gui::TrustedOverlay::ENABLED);
+
+    Region touch{Rect{0, 0, 1000, 1000}};
+    setTouchableRegion(1, touch);
+    setTouchableRegion(11, touch);
+    setTouchableRegion(111, touch);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // disable trusted overlay but flag is disabled so this behaves
+    // as UNSET
+    setTrustedOverlay(11, gui::TrustedOverlay::DISABLED);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+
+    // unset state and go back to default behavior of inheriting
+    // state
+    setTrustedOverlay(11, gui::TrustedOverlay::UNSET);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(11)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
+static constexpr const FloatRect LARGE_FLOAT_RECT{std::numeric_limits<float>::min(),
+                                                  std::numeric_limits<float>::min(),
+                                                  std::numeric_limits<float>::max(),
+                                                  std::numeric_limits<float>::max()};
+TEST_F(LayerSnapshotTest, layerVisibleByDefault) {
+    DisplayInfo info;
+    info.info.logicalHeight = 1000000;
+    info.info.logicalWidth = 1000000;
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_FALSE(getSnapshot(1)->isHiddenByPolicy());
+}
+
+TEST_F(LayerSnapshotTest, hideLayerWithZeroMatrix) {
+    DisplayInfo info;
+    info.info.logicalHeight = 1000000;
+    info.info.logicalWidth = 1000000;
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+    setMatrix(1, 0.f, 0.f, 0.f, 0.f);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->isHiddenByPolicy());
+}
+
+TEST_F(LayerSnapshotTest, hideLayerWithInfMatrix) {
+    DisplayInfo info;
+    info.info.logicalHeight = 1000000;
+    info.info.logicalWidth = 1000000;
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+    setMatrix(1, std::numeric_limits<float>::infinity(), 0.f, 0.f,
+              std::numeric_limits<float>::infinity());
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->isHiddenByPolicy());
+}
+
+TEST_F(LayerSnapshotTest, hideLayerWithNanMatrix) {
+    DisplayInfo info;
+    info.info.logicalHeight = 1000000;
+    info.info.logicalWidth = 1000000;
+    mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+    setMatrix(1, std::numeric_limits<float>::quiet_NaN(), 0.f, 0.f,
+              std::numeric_limits<float>::quiet_NaN());
+    UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+    EXPECT_TRUE(getSnapshot(1)->isHiddenByPolicy());
+}
+
+TEST_F(LayerSnapshotTest, edgeExtensionPropagatesInHierarchy) {
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    setCrop(1, Rect(0, 0, 20, 20));
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(20 /* width */,
+                                                                        20 /* height */,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    setEdgeExtensionEffect(12, LEFT);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot({.id = 12})->edgeExtensionEffect.extendsEdge(LEFT));
+    EXPECT_TRUE(getSnapshot({.id = 121})->edgeExtensionEffect.extendsEdge(LEFT));
+    EXPECT_TRUE(getSnapshot({.id = 1221})->edgeExtensionEffect.extendsEdge(LEFT));
+
+    setEdgeExtensionEffect(12, RIGHT);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot({.id = 12})->edgeExtensionEffect.extendsEdge(RIGHT));
+    EXPECT_TRUE(getSnapshot({.id = 121})->edgeExtensionEffect.extendsEdge(RIGHT));
+    EXPECT_TRUE(getSnapshot({.id = 1221})->edgeExtensionEffect.extendsEdge(RIGHT));
+
+    setEdgeExtensionEffect(12, TOP);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot({.id = 12})->edgeExtensionEffect.extendsEdge(TOP));
+    EXPECT_TRUE(getSnapshot({.id = 121})->edgeExtensionEffect.extendsEdge(TOP));
+    EXPECT_TRUE(getSnapshot({.id = 1221})->edgeExtensionEffect.extendsEdge(TOP));
+
+    setEdgeExtensionEffect(12, BOTTOM);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot({.id = 12})->edgeExtensionEffect.extendsEdge(BOTTOM));
+    EXPECT_TRUE(getSnapshot({.id = 121})->edgeExtensionEffect.extendsEdge(BOTTOM));
+    EXPECT_TRUE(getSnapshot({.id = 1221})->edgeExtensionEffect.extendsEdge(BOTTOM));
+}
+
+TEST_F(LayerSnapshotTest, leftEdgeExtensionIncreaseBoundSizeWithinCrop) {
+    // The left bound is extended when shifting to the right
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    setCrop(1, Rect(0, 0, 20, 20));
+    const int texSize = 10;
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(texSize /* width */,
+                                                                        texSize /* height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    const float translation = 5.0;
+    setPosition(12, translation, 0);
+    setEdgeExtensionEffect(12, LEFT);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->transformedBounds.right, texSize + translation);
+    EXPECT_LT(getSnapshot({.id = 1221})->transformedBounds.left, translation);
+    EXPECT_GE(getSnapshot({.id = 1221})->transformedBounds.left, 0.0);
+}
+
+TEST_F(LayerSnapshotTest, rightEdgeExtensionIncreaseBoundSizeWithinCrop) {
+    // The right bound is extended when shifting to the left
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    const int crop = 20;
+    setCrop(1, Rect(0, 0, crop, crop));
+    const int texSize = 10;
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(texSize /* width */,
+                                                                        texSize /* height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    const float translation = -5.0;
+    setPosition(12, translation, 0);
+    setEdgeExtensionEffect(12, RIGHT);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->transformedBounds.left, 0);
+    EXPECT_GT(getSnapshot({.id = 1221})->transformedBounds.right, texSize + translation);
+    EXPECT_LE(getSnapshot({.id = 1221})->transformedBounds.right, (float)crop);
+}
+
+TEST_F(LayerSnapshotTest, topEdgeExtensionIncreaseBoundSizeWithinCrop) {
+    // The top bound is extended when shifting to the bottom
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    setCrop(1, Rect(0, 0, 20, 20));
+    const int texSize = 10;
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(texSize /* width */,
+                                                                        texSize /* height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    const float translation = 5.0;
+    setPosition(12, 0, translation);
+    setEdgeExtensionEffect(12, TOP);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->transformedBounds.bottom, texSize + translation);
+    EXPECT_LT(getSnapshot({.id = 1221})->transformedBounds.top, translation);
+    EXPECT_GE(getSnapshot({.id = 1221})->transformedBounds.top, 0.0);
+}
+
+TEST_F(LayerSnapshotTest, bottomEdgeExtensionIncreaseBoundSizeWithinCrop) {
+    // The bottom bound is extended when shifting to the top
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    const int crop = 20;
+    setCrop(1, Rect(0, 0, crop, crop));
+    const int texSize = 10;
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(texSize /* width */,
+                                                                        texSize /* height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    const float translation = -5.0;
+    setPosition(12, 0, translation);
+    setEdgeExtensionEffect(12, BOTTOM);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_EQ(getSnapshot({.id = 1221})->transformedBounds.top, 0);
+    EXPECT_GT(getSnapshot({.id = 1221})->transformedBounds.bottom, texSize - translation);
+    EXPECT_LE(getSnapshot({.id = 1221})->transformedBounds.bottom, (float)crop);
+}
+
+TEST_F(LayerSnapshotTest, multipleEdgeExtensionIncreaseBoundSizeWithinCrop) {
+    // The left bound is extended when shifting to the right
+    if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
+        GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
+    }
+    const int crop = 20;
+    setCrop(1, Rect(0, 0, crop, crop));
+    const int texSize = 10;
+    setBuffer(1221,
+              std::make_shared<renderengine::mock::FakeExternalTexture>(texSize /* width */,
+                                                                        texSize /* height*/,
+                                                                        42ULL /* bufferId */,
+                                                                        HAL_PIXEL_FORMAT_RGBA_8888,
+                                                                        0 /*usage*/));
+    const float translation = 5.0;
+    setPosition(12, translation, translation);
+    setEdgeExtensionEffect(12, LEFT | RIGHT | TOP | BOTTOM);
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+    EXPECT_GT(getSnapshot({.id = 1221})->transformedBounds.right, texSize + translation);
+    EXPECT_LE(getSnapshot({.id = 1221})->transformedBounds.right, (float)crop);
+    EXPECT_LT(getSnapshot({.id = 1221})->transformedBounds.left, translation);
+    EXPECT_GE(getSnapshot({.id = 1221})->transformedBounds.left, 0.0);
+    EXPECT_GT(getSnapshot({.id = 1221})->transformedBounds.bottom, texSize + translation);
+    EXPECT_LE(getSnapshot({.id = 1221})->transformedBounds.bottom, (float)crop);
+    EXPECT_LT(getSnapshot({.id = 1221})->transformedBounds.top, translation);
+    EXPECT_GE(getSnapshot({.id = 1221})->transformedBounds.top, 0);
+}
+
+TEST_F(LayerSnapshotTest, shouldUpdateInputWhenNoInputInfo) {
+    // By default the layer has no buffer, so we don't expect it to have an input info
+    EXPECT_FALSE(getSnapshot(111)->hasInputInfo());
+
+    setBuffer(111);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(111)->hasInputInfo());
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL));
+    EXPECT_FALSE(getSnapshot(2)->hasInputInfo());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerTest.cpp b/services/surfaceflinger/tests/unittests/LayerTest.cpp
deleted file mode 100644
index 95e54f6..0000000
--- a/services/surfaceflinger/tests/unittests/LayerTest.cpp
+++ /dev/null
@@ -1,86 +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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gtest/gtest.h>
-#include <ui/FloatRect.h>
-#include <ui/Transform.h>
-#include <limits>
-
-#include "LayerTestUtils.h"
-#include "TestableSurfaceFlinger.h"
-
-namespace android {
-namespace {
-
-class LayerTest : public BaseLayerTest {
-protected:
-    static constexpr const float MIN_FLOAT = std::numeric_limits<float>::min();
-    static constexpr const float MAX_FLOAT = std::numeric_limits<float>::max();
-    static constexpr const FloatRect LARGE_FLOAT_RECT{MIN_FLOAT, MIN_FLOAT, MAX_FLOAT, MAX_FLOAT};
-};
-
-INSTANTIATE_TEST_SUITE_P(PerLayerType, LayerTest,
-                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
-                                         std::make_shared<EffectLayerFactory>()),
-                         PrintToStringParamName);
-
-TEST_P(LayerTest, layerVisibleByDefault) {
-    sp<Layer> layer = GetParam()->createLayer(mFlinger);
-    layer->updateGeometry();
-    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
-    ASSERT_FALSE(layer->isHiddenByPolicy());
-}
-
-TEST_P(LayerTest, hideLayerWithZeroMatrix) {
-    sp<Layer> layer = GetParam()->createLayer(mFlinger);
-
-    layer_state_t::matrix22_t matrix{0, 0, 0, 0};
-    layer->setMatrix(matrix);
-    layer->updateGeometry();
-    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
-
-    ASSERT_TRUE(layer->isHiddenByPolicy());
-}
-
-TEST_P(LayerTest, hideLayerWithInfMatrix) {
-    sp<Layer> layer = GetParam()->createLayer(mFlinger);
-
-    constexpr const float INF = std::numeric_limits<float>::infinity();
-    layer_state_t::matrix22_t matrix{INF, 0, 0, INF};
-    layer->setMatrix(matrix);
-    layer->updateGeometry();
-    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
-
-    ASSERT_TRUE(layer->isHiddenByPolicy());
-}
-
-TEST_P(LayerTest, hideLayerWithNanMatrix) {
-    sp<Layer> layer = GetParam()->createLayer(mFlinger);
-
-    constexpr const float QUIET_NAN = std::numeric_limits<float>::quiet_NaN();
-    layer_state_t::matrix22_t matrix{QUIET_NAN, 0, 0, QUIET_NAN};
-    layer->setMatrix(matrix);
-    layer->updateGeometry();
-    layer->computeBounds(LARGE_FLOAT_RECT, ui::Transform(), 0.f);
-
-    ASSERT_TRUE(layer->isHiddenByPolicy());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 9c66a97..c879280 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -18,7 +18,11 @@
 #define LOG_TAG "PowerAdvisorTest"
 
 #include <DisplayHardware/PowerAdvisor.h>
+#include <android_os.h>
 #include <binder/Status.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <powermanager/PowerHalWrapper.h>
@@ -26,8 +30,8 @@
 #include <chrono>
 #include <future>
 #include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
 #include "mock/DisplayHardware/MockPowerHalController.h"
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
 
 using namespace android;
 using namespace android::Hwc2::mock;
@@ -41,6 +45,7 @@
 class PowerAdvisorTest : public testing::Test {
 public:
     void SetUp() override;
+    void SetUpFmq(bool usesSharedEventFlag, bool isQueueFull);
     void startPowerHintSession(bool returnValidSession = true);
     void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod);
     void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime);
@@ -49,12 +54,37 @@
     void setTimingTestingMode(bool testinMode);
     void allowReportActualToAcquireMutex();
     bool sessionExists();
+    int64_t toNanos(Duration d);
+
+    struct GpuTestConfig {
+        bool adpfGpuFlagOn;
+        Duration frame1GpuFenceDuration;
+        Duration frame2GpuFenceDuration;
+        Duration vsyncPeriod;
+        Duration presentDuration = 0ms;
+        Duration postCompDuration = 0ms;
+        bool frame1RequiresRenderEngine;
+        bool frame2RequiresRenderEngine;
+        bool usesFmq = false;
+        bool usesSharedFmqFlag = true;
+        bool fmqFull = false;
+    };
+
+    void testGpuScenario(GpuTestConfig& config, WorkDuration& ret);
 
 protected:
     TestableSurfaceFlinger mFlinger;
     std::unique_ptr<PowerAdvisor> mPowerAdvisor;
     MockPowerHalController* mMockPowerHalController;
-    std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
+    std::shared_ptr<MockPowerHintSessionWrapper> mMockPowerHintSession;
+    std::shared_ptr<AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>> mBackendFmq;
+    std::shared_ptr<AidlMessageQueue<int8_t, SynchronizedReadWrite>> mBackendFlagQueue;
+    android::hardware::EventFlag* mEventFlag;
+    uint32_t mWriteFlagBitmask = 2;
+    uint32_t mReadFlagBitmask = 1;
+    int64_t mSessionId = 123;
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel, true);
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, false);
 };
 
 bool PowerAdvisorTest::sessionExists() {
@@ -62,31 +92,72 @@
     return mPowerAdvisor->mHintSession != nullptr;
 }
 
+int64_t PowerAdvisorTest::toNanos(Duration d) {
+    return std::chrono::nanoseconds(d).count();
+}
+
 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)));
+            .WillByDefault(Return(
+                    ByMove(HalResult<int64_t>::fromStatus(ndk::ScopedAStatus::ok(), 16000))));
+}
+
+void PowerAdvisorTest::SetUpFmq(bool usesSharedEventFlag, bool isQueueFull) {
+    mBackendFmq = std::make_shared<
+            AidlMessageQueue<ChannelMessage, SynchronizedReadWrite>>(2, !usesSharedEventFlag);
+    ChannelConfig config;
+    config.channelDescriptor = mBackendFmq->dupeDesc();
+    if (usesSharedEventFlag) {
+        mBackendFlagQueue =
+                std::make_shared<AidlMessageQueue<int8_t, SynchronizedReadWrite>>(1, true);
+        config.eventFlagDescriptor = mBackendFlagQueue->dupeDesc();
+        ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFlagQueue
+                                                                        ->getEventFlagWord(),
+                                                                &mEventFlag),
+                  android::NO_ERROR);
+    } else {
+        ASSERT_EQ(android::hardware::EventFlag::createEventFlag(mBackendFmq->getEventFlagWord(),
+                                                                &mEventFlag),
+                  android::NO_ERROR);
+    }
+    config.writeFlagBitmask = static_cast<int32_t>(mWriteFlagBitmask);
+    config.readFlagBitmask = static_cast<int32_t>(mReadFlagBitmask);
+    ON_CALL(*mMockPowerHalController, getSessionChannel)
+            .WillByDefault(Return(
+                    ByMove(HalResult<ChannelConfig>::fromStatus(Status::ok(), std::move(config)))));
+    startPowerHintSession();
+    if (isQueueFull) {
+        std::vector<ChannelMessage> msgs;
+        msgs.resize(2);
+        mBackendFmq->writeBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask,
+                                   std::chrono::nanoseconds(1ms).count(), mEventFlag);
+    }
 }
 
 void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
-    mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+    mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
     if (returnValidSession) {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(
-                        Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                       fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+                .WillByDefault(DoAll(SetArgPointee<5>(aidl::android::hardware::power::SessionConfig{
+                                             .id = mSessionId}),
+                                     Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                                    fromStatus(binder::Status::ok(),
+                                                               mMockPowerHintSession))));
     } else {
-        ON_CALL(*mMockPowerHalController, createHintSession)
-                .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
-                                              fromStatus(binder::Status::ok(), nullptr)));
+        ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
+            return HalResult<
+                    std::shared_ptr<PowerHintSessionWrapper>>::fromStatus(ndk::ScopedAStatus::ok(),
+                                                                          nullptr);
+        });
     }
     mPowerAdvisor->enablePowerHintSession(true);
     mPowerAdvisor->startPowerHintSession({1, 2, 3});
     ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
-            .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillByDefault(Return(testing::ByMove(HalResult<void>::ok())));
 }
 
 void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration,
@@ -109,6 +180,131 @@
     mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
 }
 
+void PowerAdvisorTest::testGpuScenario(GpuTestConfig& config, WorkDuration& ret) {
+    SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::adpf_gpu_sf,
+                      config.adpfGpuFlagOn);
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, config.usesFmq);
+    mPowerAdvisor->onBootFinished();
+    bool expectsFmqSuccess = config.usesSharedFmqFlag && !config.fmqFull;
+    if (config.usesFmq) {
+        SetUpFmq(config.usesSharedFmqFlag, config.fmqFull);
+    } else {
+        startPowerHintSession();
+    }
+
+    std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u), GpuVirtualDisplayId(0),
+                                      GpuVirtualDisplayId(1)};
+    mPowerAdvisor->setDisplays(displayIds);
+    auto display1 = displayIds[0];
+    // 60hz
+
+    TimePoint startTime = TimePoint::now();
+    int64_t target;
+    SessionHint hint;
+    if (!config.usesFmq || !expectsFmqSuccess) {
+        EXPECT_CALL(*mMockPowerHintSession, updateTargetWorkDuration(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&target),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+        EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    }
+    // advisor only starts on frame 2 so do an initial frame
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    // send a load hint
+    mPowerAdvisor->notifyCpuLoadUp();
+    if (config.usesFmq && expectsFmqSuccess) {
+        std::vector<ChannelMessage> msgs;
+        ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+        msgs.resize(2);
+        ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 2, mReadFlagBitmask, mWriteFlagBitmask,
+                                              std::chrono::nanoseconds(1ms).count(), mEventFlag));
+        ASSERT_EQ(msgs[0].sessionID, mSessionId);
+        ASSERT_GE(msgs[0].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[0].data.getTag(),
+                  ChannelMessage::ChannelMessageContents::Tag::targetDuration);
+        target = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::targetDuration>();
+        ASSERT_EQ(msgs[1].sessionID, mSessionId);
+        ASSERT_GE(msgs[1].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[1].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint);
+        hint = msgs[1].data.get<ChannelMessage::ChannelMessageContents::Tag::hint>();
+    }
+    ASSERT_EQ(target, config.vsyncPeriod.ns());
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame1RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame1GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame1GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+
+    // increment the frame
+    std::this_thread::sleep_for(config.vsyncPeriod);
+    startTime = TimePoint::now();
+    fakeBasicFrameTiming(startTime, config.vsyncPeriod);
+    if (config.usesFmq && expectsFmqSuccess) {
+        // same target update will not trigger FMQ write
+        ASSERT_EQ(mBackendFmq->availableToRead(), 0uL);
+    }
+    setExpectedTiming(config.vsyncPeriod, startTime + config.vsyncPeriod);
+
+    // report GPU
+    mPowerAdvisor->setRequiresRenderEngine(display1, config.frame2RequiresRenderEngine);
+    if (config.adpfGpuFlagOn) {
+        mPowerAdvisor->setGpuStartTime(display1, startTime);
+    }
+    if (config.frame2GpuFenceDuration.count() == Fence::SIGNAL_TIME_PENDING) {
+        mPowerAdvisor->setGpuFenceTime(display1,
+                                       std::make_unique<FenceTime>(Fence::SIGNAL_TIME_PENDING));
+    } else {
+        TimePoint end = startTime + config.frame2GpuFenceDuration;
+        mPowerAdvisor->setGpuFenceTime(display1, std::make_unique<FenceTime>(end.ns()));
+    }
+    mPowerAdvisor->setSfPresentTiming(startTime, startTime + config.presentDuration);
+    mPowerAdvisor->setCompositeEnd(startTime + config.presentDuration + config.postCompDuration);
+
+    // don't report timing for the HWC
+    mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime, startTime);
+    mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime, startTime);
+
+    if (config.usesFmq && expectsFmqSuccess) {
+        mPowerAdvisor->reportActualWorkDuration();
+        ASSERT_EQ(mBackendFmq->availableToRead(), 1uL);
+        std::vector<ChannelMessage> msgs;
+        msgs.resize(1);
+        ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                                              std::chrono::nanoseconds(1ms).count(), mEventFlag));
+        ASSERT_EQ(msgs[0].sessionID, mSessionId);
+        ASSERT_GE(msgs[0].timeStampNanos, startTime.ns());
+        ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::workDuration);
+        auto actual = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::workDuration>();
+        ret.workPeriodStartTimestampNanos = actual.workPeriodStartTimestampNanos;
+        ret.cpuDurationNanos = actual.cpuDurationNanos;
+        ret.gpuDurationNanos = actual.gpuDurationNanos;
+        ret.durationNanos = actual.durationNanos;
+    } else {
+        std::vector<aidl::android::hardware::power::WorkDuration> durationReq;
+        EXPECT_CALL(*mMockPowerHintSession, reportActualWorkDuration(_))
+                .Times(1)
+                .WillOnce(DoAll(testing::SaveArg<0>(&durationReq),
+                                testing::Return(testing::ByMove(HalResult<void>::ok()))));
+        mPowerAdvisor->reportActualWorkDuration();
+        ASSERT_EQ(durationReq.size(), 1u);
+        ret = std::move(durationReq[0]);
+    }
+}
+
 Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
     return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
                          : PowerAdvisor::kFenceWaitStartDelayValidated);
@@ -148,7 +344,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
     mPowerAdvisor->setDisplays(displayIds);
@@ -188,7 +384,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -231,7 +427,7 @@
                 reportActualWorkDuration(ElementsAre(
                         Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
             .Times(1)
-            .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+            .WillOnce(Return(testing::ByMove(HalResult<void>::ok())));
 
     fakeBasicFrameTiming(startTime, vsyncPeriod);
     setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
@@ -282,13 +478,6 @@
     mPowerAdvisor->reportActualWorkDuration();
 }
 
-TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
-    EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
-    mPowerAdvisor->onBootFinished();
-    startPowerHintSession();
-    mPowerAdvisor->startPowerHintSession({1, 2, 3});
-}
-
 TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) {
     // notifyDisplayUpdateImminentAndCpuReset or notifyCpuLoadUp gets called in background
     // reportActual gets called during callback and sees true session, passes ensure
@@ -328,17 +517,17 @@
 
     ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] {
         letSendHintFinish.get_future().wait();
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
     ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] {
-        return ndk::ScopedAStatus::fromExceptionCode(-127);
+        return HalResult<void>::fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127));
     });
 
-    ON_CALL(*mMockPowerHalController, createHintSession)
-            .WillByDefault(Return(
-                    HalResult<std::shared_ptr<IPowerHintSession>>::
-                            fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
+    ON_CALL(*mMockPowerHalController, createHintSessionWithConfig).WillByDefault([] {
+        return HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr);
+    });
 
     // First background call, to notice the session is down
     auto firstHint = std::async(std::launch::async, [this] {
@@ -370,5 +559,284 @@
     EXPECT_EQ(sessionExists(), false);
 }
 
+TEST_F(PowerAdvisorTest, legacyHintSessionCreationStillWorks) {
+    mPowerAdvisor->onBootFinished();
+    mMockPowerHintSession = std::make_shared<NiceMock<MockPowerHintSessionWrapper>>();
+    EXPECT_CALL(*mMockPowerHalController, createHintSessionWithConfig)
+            .Times(1)
+            .WillOnce(Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                     fromStatus(ndk::ScopedAStatus::fromExceptionCode(
+                                                        EX_UNSUPPORTED_OPERATION),
+                                                nullptr)));
+
+    EXPECT_CALL(*mMockPowerHalController, createHintSession)
+            .Times(1)
+            .WillOnce(Return(HalResult<std::shared_ptr<PowerHintSessionWrapper>>::
+                                     fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+    mPowerAdvisor->enablePowerHintSession(true);
+    ASSERT_TRUE(mPowerAdvisor->startPowerHintSession({1, 2, 3}));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked buffer fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(30ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_cpuThenGpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = false,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(30ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // faked fence time for testing
+            .frame1GpuFenceDuration = 41ms,
+            .frame2GpuFenceDuration = 31ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_gpuThenCpuFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 40ms,
+            .frame2GpuFenceDuration = 30ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(10ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFrames) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            // added a margin as a workaround since we set GPU start time at the time of fence set
+            // call
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = 51ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(50ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(51ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_twoSignaledGpuFenceFrames_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = 50ms,
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(50ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(10ms));
+    EXPECT_EQ(res.durationNanos, toNanos(50ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = false,
+            .frame1GpuFenceDuration = 31ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 2ms,
+            .postCompDuration = 8ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, 0L);
+    EXPECT_EQ(res.cpuDurationNanos, 0L);
+    EXPECT_GE(res.durationNanos, toNanos(29ms + getErrorMargin()));
+    EXPECT_LE(res.durationNanos, toNanos(31ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, setGpuFenceTime_UnsingaledGpuFenceFrameUsingPreviousFrame_flagOn) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+            .usesFmq = true,
+            .usesSharedFmqFlag = true,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_noSharedFlag) {
+    GpuTestConfig config{
+            .adpfGpuFlagOn = true,
+            .frame1GpuFenceDuration = 30ms,
+            .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+            .vsyncPeriod = 10ms,
+            .presentDuration = 22ms,
+            .postCompDuration = 88ms,
+            .frame1RequiresRenderEngine = true,
+            .frame2RequiresRenderEngine = true,
+            .usesFmq = true,
+            .usesSharedFmqFlag = false,
+    };
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendTargetAndActualDuration_queueFull) {
+    GpuTestConfig config{.adpfGpuFlagOn = true,
+                         .frame1GpuFenceDuration = 30ms,
+                         .frame2GpuFenceDuration = Duration::fromNs(Fence::SIGNAL_TIME_PENDING),
+                         .vsyncPeriod = 10ms,
+                         .presentDuration = 22ms,
+                         .postCompDuration = 88ms,
+                         .frame1RequiresRenderEngine = true,
+                         .frame2RequiresRenderEngine = true,
+                         .usesFmq = true,
+                         .usesSharedFmqFlag = true,
+                         .fmqFull = true};
+    WorkDuration res;
+    testGpuScenario(config, res);
+    EXPECT_EQ(res.gpuDurationNanos, toNanos(30ms));
+    EXPECT_EQ(res.cpuDurationNanos, toNanos(110ms));
+    EXPECT_EQ(res.durationNanos, toNanos(110ms + getErrorMargin()));
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(true, false);
+    auto startTime = uptimeNanos();
+    mPowerAdvisor->notifyCpuLoadUp();
+    std::vector<ChannelMessage> msgs;
+    ASSERT_EQ(mBackendFmq->availableToRead(), 1uL);
+    msgs.resize(1);
+    ASSERT_TRUE(mBackendFmq->readBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                                          std::chrono::nanoseconds(1ms).count(), mEventFlag));
+    ASSERT_EQ(msgs[0].sessionID, mSessionId);
+    ASSERT_GE(msgs[0].timeStampNanos, startTime);
+    ASSERT_EQ(msgs[0].data.getTag(), ChannelMessage::ChannelMessageContents::Tag::hint);
+    auto hint = msgs[0].data.get<ChannelMessage::ChannelMessageContents::Tag::hint>();
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint_noSharedFlag) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(false, false);
+    SessionHint hint;
+    EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+            .Times(1)
+            .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                            testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    mPowerAdvisor->notifyCpuLoadUp();
+    ASSERT_EQ(mBackendFmq->availableToRead(), 0uL);
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
+TEST_F(PowerAdvisorTest, fmq_sendHint_queueFull) {
+    SET_FLAG_FOR_TEST(android::os::adpf_use_fmq_channel_fixed, true);
+    mPowerAdvisor->onBootFinished();
+    SetUpFmq(true, true);
+    ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+    SessionHint hint;
+    EXPECT_CALL(*mMockPowerHintSession, sendHint(_))
+            .Times(1)
+            .WillOnce(DoAll(testing::SaveArg<0>(&hint),
+                            testing::Return(testing::ByMove(HalResult<void>::ok()))));
+    std::vector<ChannelMessage> msgs;
+    msgs.resize(1);
+    mBackendFmq->writeBlocking(msgs.data(), 1, mReadFlagBitmask, mWriteFlagBitmask,
+                               std::chrono::nanoseconds(1ms).count(), mEventFlag);
+    mPowerAdvisor->notifyCpuLoadUp();
+    ASSERT_EQ(mBackendFmq->availableToRead(), 2uL);
+    ASSERT_EQ(hint, SessionHint::CPU_LOAD_UP);
+}
+
 } // namespace
 } // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index 0a6e305..adbd868 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -100,11 +100,14 @@
     const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
 
     using RefreshRateSelector::GetRankedFrameRatesCache;
-    auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
+    auto& mutableGetRankedRefreshRatesCache() NO_THREAD_SAFETY_ANALYSIS {
+        return mGetRankedFrameRatesCache;
+    }
 
     auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
-                             GlobalSignals signals = {}) const {
-        const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+                             GlobalSignals signals = {}, Fps pacesetterFps = {}) const {
+        const auto result =
+                RefreshRateSelector::getRankedFrameRates(layers, signals, pacesetterFps);
 
         EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
                                    ScoredFrameRate::DescendingScore{}));
@@ -114,8 +117,8 @@
 
     auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
                                      GlobalSignals signals) const {
-        const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
-        return std::make_pair(ranking, consideredSignals);
+        const auto result = getRankedFrameRates(layers, signals);
+        return std::make_pair(result.ranking, result.consideredSignals);
     }
 
     FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
@@ -137,7 +140,9 @@
         return setPolicy(policy);
     }
 
-    const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
+    const auto& getPrimaryFrameRates() const NO_THREAD_SAFETY_ANALYSIS {
+        return mPrimaryFrameRates;
+    }
 };
 
 class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
@@ -259,6 +264,50 @@
         config.enableFrameRateOverride = GetParam();
         return TestableRefreshRateSelector(modes, activeModeId, config);
     }
+
+    template <class T>
+    void testFrameRateCategoryWithMultipleLayers(const std::initializer_list<T>& testCases,
+                                                 const TestableRefreshRateSelector& selector) {
+        std::vector<LayerRequirement> layers;
+        for (auto testCase : testCases) {
+            ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+                  to_string(testCase.desiredFrameRate).c_str(),
+                  ftl::enum_string(testCase.frameRateCategory).c_str());
+
+            if (testCase.desiredFrameRate.isValid()) {
+                std::stringstream ss;
+                ss << to_string(testCase.desiredFrameRate)
+                   << ftl::enum_string(testCase.frameRateCategory) << "ExplicitDefault";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitDefault,
+                                          .desiredRefreshRate = testCase.desiredFrameRate,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            if (testCase.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = testCase.frameRateCategory,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            EXPECT_EQ(testCase.expectedFrameRate,
+                      selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+                    << "Did not get expected frame rate for frameRate="
+                    << to_string(testCase.desiredFrameRate)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " frameRate=" << to_string(testCase.desiredFrameRate)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+        }
+    }
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1343,7 +1392,7 @@
 TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+    auto [refreshRates, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
 
     auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
@@ -1427,10 +1476,32 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, pacesetterConsidered) {
+    auto selector = createSelector(kModes_60_90, kModeId60);
+    constexpr RefreshRateSelector::GlobalSignals kNoSignals;
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+    layers[0].vote = LayerVoteType::Min;
+
+    // The pacesetterFps takes precedence over the LayerRequirement.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {}, 90_Hz);
+        EXPECT_EQ(kMode90, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+
+    // The pacesetterFps takes precedence over GlobalSignals.
+    {
+        const auto result = selector.getRankedFrameRates(layers, {.touch = true}, 60_Hz);
+        EXPECT_EQ(kMode60, result.ranking.front().frameRateMode.modePtr);
+        EXPECT_EQ(kNoSignals, result.consideredSignals);
+    }
+}
+
 TEST_P(RefreshRateSelectorTest, touchConsidered) {
     auto selector = createSelector(kModes_60_90, kModeId60);
 
-    auto [_, signals] = selector.getRankedFrameRates({}, {});
+    auto signals = selector.getRankedFrameRates({}, {}).consideredSignals;
     EXPECT_FALSE(signals.touch);
 
     std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
@@ -1495,8 +1566,8 @@
             // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
             {0_Hz, FrameRateCategory::High, 90_Hz},
             {0_Hz, FrameRateCategory::Normal, 60_Hz},
-            {0_Hz, FrameRateCategory::Low, 30_Hz},
-            {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+            {0_Hz, FrameRateCategory::Low, 60_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
 
             // Cases that have both desired frame rate and frame rate category requirements.
             {24_Hz, FrameRateCategory::High, 120_Hz},
@@ -1542,6 +1613,169 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_120_vrr) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    // Device with VRR config mode
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+    };
+
+    // Prepare a table with the vote and the expected refresh rate
+    const std::initializer_list<Case> testCases = {
+            // Cases that only have frame rate category requirements, but no desired frame rate.
+            // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+            {0_Hz, FrameRateCategory::High, 120_Hz},
+            {0_Hz, FrameRateCategory::Normal, 60_Hz},
+            {0_Hz, FrameRateCategory::Low, 48_Hz},
+            {0_Hz, FrameRateCategory::NoPreference, 120_Hz},
+
+            // Cases that have both desired frame rate and frame rate category requirements.
+            {24_Hz, FrameRateCategory::High, 120_Hz},
+            {30_Hz, FrameRateCategory::High, 120_Hz},
+            {12_Hz, FrameRateCategory::Normal, 60_Hz},
+            {24_Hz, FrameRateCategory::Low, 48_Hz},
+            {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+            // Cases that only have desired frame rate.
+            {30_Hz, FrameRateCategory::Default, 30_Hz},
+    };
+
+    for (auto testCase : testCases) {
+        std::vector<LayerRequirement> layers;
+        ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+              to_string(testCase.desiredFrameRate).c_str(),
+              ftl::enum_string(testCase.frameRateCategory).c_str());
+
+        if (testCase.desiredFrameRate.isValid()) {
+            std::stringstream ss;
+            ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitDefault,
+                                      .desiredRefreshRate = testCase.desiredFrameRate,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        if (testCase.frameRateCategory != FrameRateCategory::Default) {
+            std::stringstream ss;
+            ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+            LayerRequirement layer = {.name = ss.str(),
+                                      .vote = LayerVoteType::ExplicitCategory,
+                                      .frameRateCategory = testCase.frameRateCategory,
+                                      .weight = 1.f};
+            layers.push_back(layer);
+        }
+
+        EXPECT_EQ(testCase.expectedFrameRate, selector.getBestFrameRateMode(layers).fps)
+                << "Did not get expected frame rate for frameRate="
+                << to_string(testCase.desiredFrameRate)
+                << " category=" << ftl::enum_string(testCase.frameRateCategory);
+    }
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategoryMultiLayers_30_60_90_120) {
+    auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId90;
+    };
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {0_Hz, FrameRateCategory::High, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+                    {0_Hz, FrameRateCategory::Normal, 90_Hz},
+                    {0_Hz, FrameRateCategory::Normal, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {0_Hz, FrameRateCategory::Normal, 60_Hz, kModeId60},
+                    {0_Hz, FrameRateCategory::High, 90_Hz},
+                    {0_Hz, FrameRateCategory::NoPreference, 90_Hz},
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {30_Hz, FrameRateCategory::High, 90_Hz},
+                    {24_Hz, FrameRateCategory::High, 120_Hz, kModeId120},
+                    {12_Hz, FrameRateCategory::Normal, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::NoPreference, 120_Hz, kModeId120},
+
+            },
+            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {24_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {30_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+                    {120_Hz, FrameRateCategory::Default, 120_Hz, kModeId120},
+            },
+            selector);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategoryMultiLayers_60_120) {
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
+
+    struct Case {
+        // Params
+        Fps desiredFrameRate = 0_Hz;
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId120;
+    };
+
+    testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+                                                    Case>{{0_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz},
+                                                          {0_Hz, FrameRateCategory::Normal, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::Normal, 120_Hz},
+                                                          {0_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz}},
+                                            selector);
+
+    testFrameRateCategoryWithMultipleLayers(std::initializer_list<
+                                                    Case>{{24_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {30_Hz, FrameRateCategory::High, 120_Hz},
+                                                          {12_Hz, FrameRateCategory::Normal,
+                                                           120_Hz},
+                                                          {30_Hz, FrameRateCategory::NoPreference,
+                                                           120_Hz}},
+                                            selector);
+
+    testFrameRateCategoryWithMultipleLayers(
+            std::initializer_list<Case>{
+                    {24_Hz, FrameRateCategory::Default, 120_Hz},
+                    {30_Hz, FrameRateCategory::Default, 120_Hz},
+                    {120_Hz, FrameRateCategory::Default, 120_Hz},
+            },
+            selector);
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
     auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
 
@@ -1607,6 +1841,43 @@
     }
 }
 
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_vrrHighHintTouch_primaryRangeIsSingleRate) {
+    if (GetParam() != Config::FrameRateOverride::Enabled) {
+        return;
+    }
+
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    auto selector = createSelector(kVrrMode_120, kModeId120);
+    selector.setActiveMode(kModeId120, 60_Hz);
+
+    // Change primary physical range to be single rate, which on VRR device should not affect
+    // fps scoring.
+    EXPECT_EQ(SetPolicyResult::Changed,
+              selector.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}));
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    layers[0].vote = LayerVoteType::ExplicitCategory;
+    layers[0].frameRateCategory = FrameRateCategory::HighHint;
+    layers[0].name = "ExplicitCategory HighHint";
+
+    auto actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Expect late touch boost from HighHint.
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    layers[1].vote = LayerVoteType::ExplicitExactOrMultiple;
+    layers[1].desiredRefreshRate = 30_Hz;
+    layers[1].name = "ExplicitExactOrMultiple 30Hz";
+
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Expect late touch boost from HighHint.
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+}
+
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_HighHint) {
     auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
 
@@ -1665,6 +1936,7 @@
     lr1.frameRateCategory = FrameRateCategory::HighHint;
     lr1.name = "ExplicitCategory HighHint";
     lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.frameRateCategory = FrameRateCategory::Default;
     lr2.desiredRefreshRate = 30_Hz;
     lr2.name = "30Hz ExplicitExactOrMultiple";
     actualRankedFrameRates = selector.getRankedFrameRates(layers);
@@ -1691,6 +1963,269 @@
         EXPECT_EQ(kModeId30, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
         EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
     }
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz Heuristic";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Min;
+    lr2.name = "Min";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::HighHint;
+    lr1.name = "ExplicitCategory HighHint";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers);
+    // Gets touch boost
+    EXPECT_EQ(120_Hz, actualRankedFrameRates.ranking.front().frameRateMode.fps);
+    EXPECT_EQ(kModeId120, actualRankedFrameRates.ranking.front().frameRateMode.modePtr->getId());
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_TouchBoost) {
+    auto selector = createSelector(makeModes(kMode24, kMode30, kMode60, kMode120), kModeId60);
+
+    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+    auto& lr1 = layers[0];
+    auto& lr2 = layers[1];
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::NoVote;
+    lr2.name = "NoVote";
+    auto actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    // No touch boost, for example a game that uses setFrameRate(30, default compatibility).
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitDefault;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitDefault";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::HighHint;
+    lr2.name = "ExplicitCategory HighHint";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitCategory;
+    lr2.frameRateCategory = FrameRateCategory::Low;
+    lr2.name = "ExplicitCategory Low";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+    lr2.frameRateCategory = FrameRateCategory::Default;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExactOrMultiple";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitExact;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitExact";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    if (selector.supportsAppFrameRateOverrideByContent()) {
+        EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+                               actualRankedFrameRates.ranking.front().frameRateMode);
+        EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+    } else {
+        EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+                               actualRankedFrameRates.ranking.front().frameRateMode);
+        EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+    }
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Min;
+    lr2.name = "Min";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Max;
+    lr2.name = "Max";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::Heuristic;
+    lr2.name = "30Hz Heuristic";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_TRUE(actualRankedFrameRates.consideredSignals.touch);
+
+    lr1.vote = LayerVoteType::ExplicitCategory;
+    lr1.frameRateCategory = FrameRateCategory::Normal;
+    lr1.name = "ExplicitCategory Normal";
+    lr2.vote = LayerVoteType::ExplicitGte;
+    lr2.desiredRefreshRate = 30_Hz;
+    lr2.name = "30Hz ExplicitGte";
+    actualRankedFrameRates = selector.getRankedFrameRates(layers, {.touch = true});
+    EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, actualRankedFrameRates.ranking.front().frameRateMode);
+    EXPECT_FALSE(actualRankedFrameRates.consideredSignals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest,
+       getBestFrameRateMode_withFrameRateCategory_idleTimer_60_120_nonVrr) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, false);
+    using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+    struct LayerArg {
+        // Params
+        FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+        LayerVoteType voteType = LayerVoteType::ExplicitDefault;
+
+        // Expected result
+        Fps expectedFrameRate = 0_Hz;
+        DisplayModeId expectedModeId = kModeId60;
+    };
+
+    const auto runTest = [&](const TestableRefreshRateSelector& selector,
+                             const std::initializer_list<LayerArg>& layerArgs,
+                             const RefreshRateSelector::GlobalSignals& signals) {
+        std::vector<LayerRequirement> layers;
+        for (auto testCase : layerArgs) {
+            ALOGI("**** %s: Testing frameRateCategory=%s", __func__,
+                  ftl::enum_string(testCase.frameRateCategory).c_str());
+
+            if (testCase.frameRateCategory != FrameRateCategory::Default) {
+                std::stringstream ss;
+                ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = LayerVoteType::ExplicitCategory,
+                                          .frameRateCategory = testCase.frameRateCategory,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            if (testCase.voteType != LayerVoteType::ExplicitDefault) {
+                std::stringstream ss;
+                ss << ftl::enum_string(testCase.voteType);
+                LayerRequirement layer = {.name = ss.str(),
+                                          .vote = testCase.voteType,
+                                          .weight = 1.f};
+                layers.push_back(layer);
+            }
+
+            EXPECT_EQ(testCase.expectedFrameRate,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getPeakFps())
+                    << "Did not get expected frame rate for"
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+            EXPECT_EQ(testCase.expectedModeId,
+                      selector.getBestFrameRateMode(layers, signals).modePtr->getId())
+                    << "Did not get expected DisplayModeId for modeId="
+                    << ftl::to_underlying(testCase.expectedModeId)
+                    << " category=" << ftl::enum_string(testCase.frameRateCategory);
+        }
+    };
+
+    {
+        // IdleTimer not configured
+        auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+        ASSERT_EQ(0ms, selector.getIdleTimerTimeout());
+
+        runTest(selector,
+                std::initializer_list<LayerArg>{
+                        // Rate does not change due to NoPreference.
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.voteType = LayerVoteType::NoVote,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                        {.frameRateCategory = FrameRateCategory::NoPreference,
+                         .expectedFrameRate = 120_Hz,
+                         .expectedModeId = kModeId120},
+                },
+                {.idle = false});
+    }
+
+    // IdleTimer configured
+    constexpr std::chrono::milliseconds kIdleTimerTimeoutMs = 10ms;
+    auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120,
+                                   Config{
+                                           .legacyIdleTimerTimeout = kIdleTimerTimeoutMs,
+                                   });
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    // Rate won't change immediately and will stay 120 due to NoPreference, as
+                    // idle timer did not timeout yet.
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 120_Hz,
+                     .expectedModeId = kModeId120},
+            },
+            {.idle = false});
+
+    // Idle timer is triggered using GlobalSignals.
+    ASSERT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+    ASSERT_EQ(kIdleTimerTimeoutMs, selector.getIdleTimerTimeout());
+    runTest(selector,
+            std::initializer_list<LayerArg>{
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.voteType = LayerVoteType::NoVote,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+                    {.frameRateCategory = FrameRateCategory::NoPreference,
+                     .expectedFrameRate = 60_Hz,
+                     .expectedModeId = kModeId60},
+            },
+            {.idle = true});
 }
 
 TEST_P(RefreshRateSelectorTest,
@@ -1716,9 +2251,8 @@
     const std::initializer_list<Case> testCases = {
             // These layers may switch modes because smoothSwitchOnly=false.
             {FrameRateCategory::Default, false, 120_Hz, kModeId120},
-            // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
-            {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
-            {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+            {FrameRateCategory::NoPreference, false, 120_Hz, kModeId120},
+            {FrameRateCategory::Low, false, 60_Hz, kModeId60},
             {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
             {FrameRateCategory::High, false, 120_Hz, kModeId120},
 
@@ -1726,7 +2260,7 @@
             // active mode (120Hz).
             {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
             {FrameRateCategory::Low, true, 120_Hz, kModeId120},
-            {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+            {FrameRateCategory::Normal, true, 120_Hz, kModeId120},
             {FrameRateCategory::High, true, 120_Hz, kModeId120},
     };
 
@@ -1785,13 +2319,13 @@
             {FrameRateCategory::Default, false, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, false, 120_Hz},
-            {FrameRateCategory::Low, false, 30_Hz},
+            {FrameRateCategory::Low, false, 48_Hz},
             {FrameRateCategory::Normal, false, 60_Hz},
             {FrameRateCategory::High, false, 120_Hz},
             {FrameRateCategory::Default, true, 120_Hz},
             // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
             {FrameRateCategory::NoPreference, true, 120_Hz},
-            {FrameRateCategory::Low, true, 30_Hz},
+            {FrameRateCategory::Low, true, 48_Hz},
             {FrameRateCategory::Normal, true, 60_Hz},
             {FrameRateCategory::High, true, 120_Hz},
     };
@@ -1964,7 +2498,7 @@
     lr.name = "60Hz ExplicitDefault";
     lr.focused = true;
 
-    const auto [rankedFrameRate, signals] =
+    const auto [rankedFrameRate, signals, _] =
             selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
 
     EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
@@ -2188,7 +2722,7 @@
     EXPECT_EQ(SetPolicyResult::Changed,
               selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
 
-    const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+    const auto [ranking, signals, _] = selector.getRankedFrameRates({}, {});
     EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
     EXPECT_FALSE(signals.touch);
 
@@ -2572,7 +3106,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
@@ -2722,16 +3256,17 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
 
     using GlobalSignals = RefreshRateSelector::GlobalSignals;
-    const auto args = std::make_pair(std::vector<LayerRequirement>{},
-                                     GlobalSignals{.touch = true, .idle = true});
-
     const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
                                                                   {90_Hz, kMode90}}},
                                                           GlobalSignals{.touch = true}};
 
-    selector.mutableGetRankedRefreshRatesCache() = {args, result};
+    selector.mutableGetRankedRefreshRatesCache() = {.layers = std::vector<LayerRequirement>{},
+                                                    .signals = GlobalSignals{.touch = true,
+                                                                             .idle = true},
+                                                    .result = result};
 
-    EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+    const auto& cache = *selector.mutableGetRankedRefreshRatesCache();
+    EXPECT_EQ(result, selector.getRankedFrameRates(cache.layers, cache.signals));
 }
 
 TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
@@ -2739,15 +3274,18 @@
 
     EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
 
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
-    RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+    const RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+    const Fps pacesetterFps = 60_Hz;
 
-    const auto result = selector.getRankedFrameRates(layers, globalSignals);
+    const auto result = selector.getRankedFrameRates(layers, globalSignals, pacesetterFps);
 
     const auto& cache = selector.mutableGetRankedRefreshRatesCache();
     ASSERT_TRUE(cache);
 
-    EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+    EXPECT_EQ(cache->layers, layers);
+    EXPECT_EQ(cache->signals, globalSignals);
+    EXPECT_EQ(cache->pacesetterFps, pacesetterFps);
     EXPECT_EQ(cache->result, result);
 }
 
@@ -3674,7 +4212,7 @@
         layers[0].vote = voteType;
         layers[0].desiredRefreshRate = 90_Hz;
 
-        const auto [ranking, signals] =
+        const auto [ranking, signals, _] =
                 selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
 
         // Refresh rate will be chosen by either touch state or idle state.
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 049b092..ac09cbc 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -34,6 +34,7 @@
 #include "mock/MockSchedulerCallback.h"
 
 #include <FrontEnd/LayerHierarchy.h>
+#include <scheduler/FrameTime.h>
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include "FpsOps.h"
@@ -57,6 +58,11 @@
 using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder;
 using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState;
 
+class ZeroClock : public Clock {
+public:
+    nsecs_t now() const override { return 0; }
+};
+
 class SchedulerTest : public testing::Test {
 protected:
     class MockEventThreadConnection : public android::EventThreadConnection {
@@ -72,6 +78,8 @@
 
     SchedulerTest();
 
+    static constexpr RefreshRateSelector::LayerRequirement kLayer = {.weight = 1.f};
+
     static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
     static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode60 =
             ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz));
@@ -79,6 +87,9 @@
             ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz));
     static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
 
+    static inline FrameRateMode kDisplay1Mode60_60{60_Hz, kDisplay1Mode60};
+    static inline FrameRateMode kDisplay1Mode120_120{120_Hz, kDisplay1Mode120};
+
     static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
     static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode60 =
             ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz));
@@ -113,10 +124,11 @@
 
     // createConnection call to scheduler makes a createEventConnection call to EventThread. Make
     // sure that call gets executed and returns an EventThread::Connection object.
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillRepeatedly(Return(mEventThreadConnection));
 
     mScheduler->setEventThread(Cycle::Render, std::move(eventThread));
+    mScheduler->setEventThread(Cycle::LastComposite, std::make_unique<MockEventThread>());
 
     mFlinger.resetScheduler(mScheduler);
 }
@@ -156,7 +168,17 @@
 
     // recordLayerHistory should be a noop
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
+    scheduler::LayerProps layerProps = {
+            .visible = true,
+            .bounds = {0, 0, 100, 100},
+            .transform = {},
+            .setFrameRateVote = {},
+            .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+            .isSmallDirty = false,
+            .isFrontBuffered = false,
+    };
+
+    mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, 0,
                                    LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
 
@@ -182,16 +204,53 @@
                                                                       kDisplay1Mode60->getId()));
 
     ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
+    scheduler::LayerProps layerProps = {
+            .visible = true,
+            .bounds = {0, 0, 100, 100},
+            .transform = {},
+            .setFrameRateVote = {},
+            .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+            .isSmallDirty = false,
+            .isFrontBuffered = false,
+    };
+    mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, 0,
                                    LayerHistory::LayerUpdateType::Buffer);
     ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
 }
 
-TEST_F(SchedulerTest, dispatchCachedReportedMode) {
-    mScheduler->clearCachedReportedMode();
+TEST_F(SchedulerTest, emitModeChangeEvent) {
+    const auto selectorPtr =
+            std::make_shared<RefreshRateSelector>(kDisplay1Modes, kDisplay1Mode120->getId());
+    mScheduler->registerDisplay(kDisplayId1, selectorPtr);
+    mScheduler->onDisplayModeChanged(kDisplayId1, kDisplay1Mode120_120);
 
+    mScheduler->setContentRequirements({kLayer});
+
+    // No event is emitted in response to idle.
     EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
-    EXPECT_NO_FATAL_FAILURE(mScheduler->dispatchCachedReportedMode());
+
+    using TimerState = TestableScheduler::TimerState;
+
+    mScheduler->idleTimerCallback(TimerState::Expired);
+    selectorPtr->setActiveMode(kDisplay1Mode60->getId(), 60_Hz);
+
+    auto layer = kLayer;
+    layer.vote = RefreshRateSelector::LayerVoteType::ExplicitExact;
+    layer.desiredRefreshRate = 60_Hz;
+    mScheduler->setContentRequirements({layer});
+
+    // An event is emitted implicitly despite choosing the same mode as when idle.
+    EXPECT_CALL(*mEventThread, onModeChanged(kDisplay1Mode60_60)).Times(1);
+
+    mScheduler->idleTimerCallback(TimerState::Reset);
+
+    mScheduler->setContentRequirements({kLayer});
+
+    // An event is emitted explicitly for the mode change.
+    EXPECT_CALL(*mEventThread, onModeChanged(kDisplay1Mode120_120)).Times(1);
+
+    mScheduler->touchTimerCallback(TimerState::Reset);
+    mScheduler->onDisplayModeChanged(kDisplayId1, kDisplay1Mode120_120);
 }
 
 TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) {
@@ -219,9 +278,16 @@
                                                                       kDisplay1Mode60->getId()));
 
     const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
-    EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
-
-    mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, systemTime(),
+    scheduler::LayerProps layerProps = {
+            .visible = true,
+            .bounds = {0, 0, 0, 0},
+            .transform = {},
+            .setFrameRateVote = {},
+            .frameRateSelectionPriority = Layer::PRIORITY_UNSET,
+            .isSmallDirty = false,
+            .isFrontBuffered = false,
+    };
+    mScheduler->recordLayerHistory(layer->getSequence(), layerProps, 0, systemTime(),
                                    LayerHistory::LayerUpdateType::Buffer);
 
     constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
@@ -240,14 +306,12 @@
                                             /*updateAttachedChoreographer*/ false);
 }
 
-TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+TEST_F(SchedulerTest, chooseDisplayModes) {
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
                                                                       kDisplay1Mode60->getId()));
 
-    std::vector<RefreshRateSelector::LayerRequirement> layers =
-            std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
-    mScheduler->setContentRequirements(layers);
+    mScheduler->setContentRequirements({kLayer, kLayer});
     GlobalSignals globalSignals = {.idle = true};
     mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
@@ -282,15 +346,14 @@
     EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
 }
 
-TEST_F(SchedulerTest, chooseDisplayModesSingleDisplayHighHintTouchSignal) {
+TEST_F(SchedulerTest, chooseDisplayModesHighHintTouchSignal) {
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
                                                                       kDisplay1Mode60->getId()));
 
     using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
 
-    std::vector<RefreshRateSelector::LayerRequirement> layers =
-            std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
+    std::vector<RefreshRateSelector::LayerRequirement> layers = {kLayer, kLayer};
     auto& lr1 = layers[0];
     auto& lr2 = layers[1];
 
@@ -338,12 +401,18 @@
 }
 
 TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
+
+    mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+    mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
 
     using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
     TestableScheduler::DisplayModeChoiceMap expectedChoices;
@@ -357,11 +426,9 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{60_Hz,
                                                                                kDisplay2Mode60},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
-        std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
-                                                                     {.weight = 1.f}};
-        mScheduler->setContentRequirements(layers);
+        mScheduler->setContentRequirements({kLayer, kLayer});
         mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
@@ -376,7 +443,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
 
@@ -395,7 +462,7 @@
                                                   globalSignals)(kDisplayId2,
                                                                  FrameRateMode{120_Hz,
                                                                                kDisplay2Mode120},
-                                                                 globalSignals);
+                                                                 GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -403,10 +470,11 @@
     {
         // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
         // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
-        mScheduler
-                ->registerDisplay(kDisplayId3,
-                                  std::make_shared<RefreshRateSelector>(kDisplay3Modes,
-                                                                        kDisplay3Mode60->getId()));
+        mScheduler->registerDisplay(kDisplayId3,
+                                    std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+                                                                          kDisplay3Mode60->getId()),
+                                    kActiveDisplayId);
+        mScheduler->setDisplayPowerMode(kDisplayId3, hal::PowerMode::ON);
 
         const GlobalSignals globalSignals = {.touch = true};
         mScheduler->replaceTouchTimer(10);
@@ -417,10 +485,10 @@
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
                                    globalSignals)(kDisplayId2,
                                                   FrameRateMode{120_Hz, kDisplay2Mode120},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                                  GlobalSignals{})(kDisplayId3,
+                                                                   FrameRateMode{60_Hz,
+                                                                                 kDisplay3Mode60},
+                                                                   GlobalSignals{});
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -435,12 +503,12 @@
         expectedChoices = ftl::init::map<
                 const PhysicalDisplayId&,
                 DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
-                                   globalSignals)(kDisplayId2,
-                                                  FrameRateMode{60_Hz, kDisplay2Mode60},
-                                                  globalSignals)(kDisplayId3,
-                                                                 FrameRateMode{60_Hz,
-                                                                               kDisplay3Mode60},
-                                                                 globalSignals);
+                                   GlobalSignals{})(kDisplayId2,
+                                                    FrameRateMode{60_Hz, kDisplay2Mode60},
+                                                    GlobalSignals{})(kDisplayId3,
+                                                                     FrameRateMode{60_Hz,
+                                                                                   kDisplay3Mode60},
+                                                                     globalSignals);
 
         const auto actualChoices = mScheduler->chooseDisplayModes();
         EXPECT_EQ(expectedChoices, actualChoices);
@@ -448,12 +516,15 @@
 }
 
 TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+    constexpr PhysicalDisplayId kActiveDisplayId = kDisplayId1;
     mScheduler->registerDisplay(kDisplayId1,
                                 std::make_shared<RefreshRateSelector>(kDisplay1Modes,
-                                                                      kDisplay1Mode60->getId()));
+                                                                      kDisplay1Mode60->getId()),
+                                kActiveDisplayId);
     mScheduler->registerDisplay(kDisplayId2,
                                 std::make_shared<RefreshRateSelector>(kDisplay2Modes,
-                                                                      kDisplay2Mode60->getId()));
+                                                                      kDisplay2Mode60->getId()),
+                                kActiveDisplayId);
 
     using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
 
@@ -564,7 +635,7 @@
                                  hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
                                                         frameRate.getPeriodNsecs())}));
     std::shared_ptr<VSyncPredictor> vrrTracker =
-            std::make_shared<VSyncPredictor>(std::make_unique<SystemClock>(), kMode, kHistorySize,
+            std::make_shared<VSyncPredictor>(std::make_unique<ZeroClock>(), kMode, kHistorySize,
                                              kMinimumSamplesForPrediction,
                                              kOutlierTolerancePercent);
     std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
@@ -576,9 +647,10 @@
                                 mFlinger.getTimeStats(),
                                 mSchedulerCallback};
 
-    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+    scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, std::nullopt,
+                              vrrTracker);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
-    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
     vrrTracker->addVsyncTimestamp(0);
     // Set 1000 as vsync seq #0
     vrrTracker->nextAnticipatedVSyncTimeFrom(700);
@@ -591,23 +663,22 @@
                                              TimePoint::fromNs(2000)));
 
     // Not crossing the min frame period
+    vrrTracker->onFrameBegin(TimePoint::fromNs(2000),
+                             {TimePoint::fromNs(1500), TimePoint::fromNs(1500)});
     EXPECT_EQ(Fps::fromPeriodNsecs(1000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
                                              TimePoint::fromNs(2500)));
     // Change render rate
     frameRate = Fps::fromPeriodNsecs(2000);
     vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
-    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
-
-    // Set 2000 as vsync seq #0
-    vrrTracker->nextAnticipatedVSyncTimeFrom(1700);
+    scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate, /*applyImmediately*/ false);
 
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(2000)));
+                                             TimePoint::fromNs(5500)));
     EXPECT_EQ(Fps::fromPeriodNsecs(2000),
               scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
-                                             TimePoint::fromNs(4000)));
+                                             TimePoint::fromNs(7500)));
 }
 
 TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
@@ -726,7 +797,7 @@
 
     const auto mockConnection1 = sp<MockEventThreadConnection>::make(mEventThread);
     const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillOnce(Return(mockConnection1))
             .WillOnce(Return(mockConnection2));
 
@@ -813,7 +884,6 @@
             mScheduler->createDisplayEventConnection(Cycle::Render, {}, layer->getHandle());
 
     layer.clear();
-    mFlinger.mutableLayersPendingRemoval().clear();
     EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
deleted file mode 100644
index 9899d42..0000000
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/FrameRateUtils.h>
-#include <gui/LayerMetadata.h>
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include "Layer.h"
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
-#include "FpsOps.h"
-#include "LayerTestUtils.h"
-#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockVsyncController.h"
-
-namespace android {
-
-using testing::DoAll;
-using testing::Mock;
-using testing::SetArgPointee;
-
-using android::Hwc2::IComposer;
-using android::Hwc2::IComposerClient;
-
-using scheduler::LayerHistory;
-
-using FrameRate = Layer::FrameRate;
-using FrameRateCompatibility = Layer::FrameRateCompatibility;
-
-/**
- * This class tests the behaviour of Layer::SetFrameRate and Layer::GetFrameRate
- */
-class SetFrameRateTest : public BaseLayerTest {
-protected:
-    const FrameRate FRAME_RATE_VOTE1 = FrameRate(67_Hz, FrameRateCompatibility::Default);
-    const FrameRate FRAME_RATE_VOTE2 = FrameRate(14_Hz, FrameRateCompatibility::ExactOrMultiple);
-    const FrameRate FRAME_RATE_VOTE3 = FrameRate(99_Hz, FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
-    const FrameRate FRAME_RATE_NO_VOTE = FrameRate(Fps(), FrameRateCompatibility::Default);
-
-    SetFrameRateTest();
-
-    void addChild(sp<Layer> layer, sp<Layer> child);
-    void removeChild(sp<Layer> layer, sp<Layer> child);
-    void commitTransaction();
-
-    std::vector<sp<Layer>> mLayers;
-};
-
-SetFrameRateTest::SetFrameRateTest() {
-    const ::testing::TestInfo* const test_info =
-            ::testing::UnitTest::GetInstance()->current_test_info();
-    ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
-
-    mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
-}
-
-void SetFrameRateTest::addChild(sp<Layer> layer, sp<Layer> child) {
-    layer->addChild(child);
-}
-
-void SetFrameRateTest::removeChild(sp<Layer> layer, sp<Layer> child) {
-    layer->removeChild(child);
-}
-
-void SetFrameRateTest::commitTransaction() {
-    for (auto layer : mLayers) {
-        layer->commitTransaction();
-    }
-}
-
-namespace {
-
-TEST_P(SetFrameRateTest, SetAndGet) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    layer->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetParent) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetParentAllVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
-    child1->setFrameRate(FRAME_RATE_VOTE2.vote);
-    parent->setFrameRate(FRAME_RATE_VOTE3.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE2, child2->getFrameRateForLayerTree());
-
-    child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE3, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE3, child2->getFrameRateForLayerTree());
-
-    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetChild) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetChildAllVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
-    child1->setFrameRate(FRAME_RATE_VOTE2.vote);
-    parent->setFrameRate(FRAME_RATE_VOTE3.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetChildAddAfterVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-
-    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-
-    addChild(child1, child2);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetChildRemoveAfterVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-
-    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-
-    removeChild(child1, child2);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-
-    parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-TEST_P(SetFrameRateTest, SetAndGetParentNotInTree) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    auto child2_1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, child2);
-    addChild(child1, child2_1);
-
-    child2->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2_1->getFrameRateForLayerTree());
-
-    child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2_1->getFrameRateForLayerTree());
-}
-
-INSTANTIATE_TEST_SUITE_P(PerLayerType, SetFrameRateTest,
-                         testing::Values(std::make_shared<BufferStateLayerFactory>(),
-                                         std::make_shared<EffectLayerFactory>()),
-                         PrintToStringParamName);
-
-TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
-    const auto& layerFactory = GetParam();
-
-    auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    auto child = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    addChild(parent, child);
-
-    parent->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-
-    auto& history = mFlinger.mutableScheduler().mutableLayerHistory();
-    history.record(parent->getSequence(), parent->getLayerProps(), 0, 0,
-                   LayerHistory::LayerUpdateType::Buffer);
-    history.record(child->getSequence(), child->getLayerProps(), 0, 0,
-                   LayerHistory::LayerUpdateType::Buffer);
-
-    const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector();
-    const auto summary = history.summarize(*selectorPtr, 0);
-
-    ASSERT_EQ(2u, summary.size());
-    EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[0].desiredRefreshRate);
-    EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[1].desiredRefreshRate);
-}
-
-TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
-    const auto& layerFactory = GetParam();
-
-    const auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    const auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    const auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-    const auto childOfChild1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
-
-    addChild(parent, child1);
-    addChild(child1, childOfChild1);
-
-    childOfChild1->setFrameRate(FRAME_RATE_VOTE1.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, childOfChild1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-
-    addChild(parent, child2);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_VOTE1, childOfChild1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-
-    childOfChild1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
-    commitTransaction();
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, childOfChild1->getFrameRateForLayerTree());
-    EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
index f127213..d638024 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ColorMatrixTest.cpp
@@ -51,7 +51,8 @@
     mFlinger.commitAndComposite();
     EXPECT_COLOR_MATRIX_CHANGED(false, false);
 
-    mFlinger.createDisplay(String8("Test Display"), false);
+    static const std::string kDisplayName("Test Display");
+    mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure=*/);
 
     mFlinger.commit();
     EXPECT_COLOR_MATRIX_CHANGED(false, true);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index 28162f4..2d3ebb4 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -27,7 +27,7 @@
 
 class CreateDisplayTest : public DisplayTransactionTest {
 public:
-    void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId,
+    void createDisplayWithRequestedRefreshRate(const std::string& name, uint64_t displayId,
                                                float pacesetterDisplayRefreshRate,
                                                float requestedRefreshRate,
                                                float expectedAdjustedRefreshRate) {
@@ -37,7 +37,7 @@
         // --------------------------------------------------------------------
         // Invocation
 
-        sp<IBinder> displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate);
+        sp<IBinder> displayToken = mFlinger.createVirtualDisplay(name, false, requestedRefreshRate);
 
         // --------------------------------------------------------------------
         // Postconditions
@@ -73,7 +73,7 @@
 };
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
-    const String8 name("virtual.test");
+    static const std::string name("virtual.test");
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -81,7 +81,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    sp<IBinder> displayToken = mFlinger.createDisplay(name, false);
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(name, false);
 
     // --------------------------------------------------------------------
     // Postconditions
@@ -97,11 +97,11 @@
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 }
 
 TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
-    const String8 name("virtual.test");
+    static const std::string kDisplayName("virtual.test");
 
     // --------------------------------------------------------------------
     // Call Expectations
@@ -112,7 +112,7 @@
     // Set the calling identity to graphics so captureDisplay with secure is allowed.
     IPCThreadState::self()->restoreCallingIdentity(static_cast<int64_t>(AID_GRAPHICS) << 32 |
                                                    AID_GRAPHICS);
-    sp<IBinder> displayToken = mFlinger.createDisplay(name, true);
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, true);
     IPCThreadState::self()->restoreCallingIdentity(oldId);
 
     // --------------------------------------------------------------------
@@ -123,89 +123,119 @@
     const auto& display = getCurrentDisplayState(displayToken);
     EXPECT_TRUE(display.isVirtual());
     EXPECT_TRUE(display.isSecure);
-    EXPECT_EQ(name.c_str(), display.displayName);
+    EXPECT_EQ(kDisplayName.c_str(), display.displayName);
 
     // --------------------------------------------------------------------
     // Cleanup conditions
 
     // Creating the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
+}
+
+TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForUniqueId) {
+    static const std::string kDisplayName("virtual.test");
+    static const std::string kUniqueId = "virtual:package:id";
+
+    // --------------------------------------------------------------------
+    // Call Expectations
+
+    // --------------------------------------------------------------------
+    // Invocation
+
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, false, kUniqueId);
+
+    // --------------------------------------------------------------------
+    // 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_FALSE(display.isSecure);
+    EXPECT_EQ(display.uniqueId, "virtual:package:id");
+    EXPECT_EQ(kDisplayName.c_str(), display.displayName);
+
+    // --------------------------------------------------------------------
+    // Cleanup conditions
+
+    // Creating the display commits a display transaction.
+    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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 0.f;
+    constexpr float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = -60.f;
+    constexpr float kExpectedAdjustedRefreshRate = 0.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 90.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 60.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 60.f;
+    constexpr float kRequestedRefreshRate = 30.f;
+    constexpr float kExpectedAdjustedRefreshRate = 30.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 120.f;
+    constexpr float kRequestedRefreshRate = 45.f;
+    constexpr float kExpectedAdjustedRefreshRate = 60.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, 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,
+    static const std::string kDisplayName("virtual.test");
+    constexpr uint64_t kDisplayId = 123ull;
+    constexpr float kPacesetterDisplayRefreshRate = 120.f;
+    constexpr float kRequestedRefreshRate = 75.f;
+    constexpr float kExpectedAdjustedRefreshRate = 120.f;
+    createDisplayWithRequestedRefreshRate(kDisplayName, kDisplayId, kPacesetterDisplayRefreshRate,
                                           kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index 93a3811..df8f68f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -38,23 +38,23 @@
     // Call Expectations
 
     // Destroying the display commits a display transaction.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.destroyDisplay(existing.token());
+    EXPECT_EQ(NO_ERROR, mFlinger.destroyVirtualDisplay(existing.token()));
 
     // --------------------------------------------------------------------
     // Postconditions
 
-    // The display should have been removed from the current state
+    // The display should have been removed from the current state.
     EXPECT_FALSE(hasCurrentDisplayState(existing.token()));
 
-    // Ths display should still exist in the drawing state
+    // Ths display should still exist in the drawing state.
     EXPECT_TRUE(hasDrawingDisplayState(existing.token()));
 
-    // The display transaction needed flasg should be set
+    // The display transaction needed flags should be set.
     EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
 }
 
@@ -67,7 +67,7 @@
     // --------------------------------------------------------------------
     // Invocation
 
-    mFlinger.destroyDisplay(displayToken);
+    EXPECT_EQ(NAME_NOT_FOUND, mFlinger.destroyVirtualDisplay(displayToken));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 15a6db6..8699621 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -42,6 +42,47 @@
 using android::hardware::graphics::composer::V2_4::Error;
 using android::hardware::graphics::composer::V2_4::VsyncPeriodChangeTimeline;
 
+MATCHER_P2(ModeSettledTo, dmc, modeId, "") {
+    const auto displayId = arg->getPhysicalId();
+
+    if (const auto desiredOpt = dmc->getDesiredMode(displayId)) {
+        *result_listener << "Unsettled desired mode "
+                         << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
+        return false;
+    }
+
+    if (dmc->getActiveMode(displayId).modePtr->getId() != modeId) {
+        *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
+        return false;
+    }
+
+    return true;
+}
+
+MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
+    const auto displayId = arg->getPhysicalId();
+    auto& dmc = flinger->mutableDisplayModeController();
+
+    if (!dmc.getDesiredMode(displayId)) {
+        *result_listener << "No desired mode";
+        return false;
+    }
+
+    if (dmc.getDesiredMode(displayId)->mode.modePtr->getId() != modeId) {
+        *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
+        return false;
+    }
+
+    // VsyncModulator should react to mode switches on the pacesetter display.
+    if (displayId == flinger->scheduler()->pacesetterDisplayId() &&
+        !flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
+        *result_listener << "VsyncModulator did not shift to early phase";
+        return false;
+    }
+
+    return true;
+}
+
 class DisplayModeSwitchingTest : public DisplayTransactionTest {
 public:
     void SetUp() override {
@@ -58,8 +99,7 @@
 
         setupScheduler(selectorPtr);
 
-        mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
-                                           DisplayHotplugEvent::CONNECTED);
+        mFlinger.onComposerHalHotplugEvent(kInnerDisplayHwcId, DisplayHotplugEvent::CONNECTED);
         mFlinger.configureAndCommit();
 
         auto vsyncController = std::make_unique<mock::VsyncController>();
@@ -76,6 +116,7 @@
         mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
                            .setRefreshRateSelector(std::move(selectorPtr))
                            .inject(std::move(vsyncController), std::move(vsyncTracker));
+        mDisplayId = mDisplay->getPhysicalId();
 
         // isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
         // will call setActiveConfig instead of setActiveConfigWithConstraints.
@@ -86,8 +127,13 @@
     static constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
     static constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
 
+    static constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+
     auto injectOuterDisplay() {
-        constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+        // For the inner display, this is handled by setupHwcHotplugCallExpectations.
+        EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
+                .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
+                                Return(hal::V2_4::Error::NONE)));
 
         constexpr bool kIsPrimary = false;
         TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL,
@@ -112,7 +158,11 @@
 protected:
     void setupScheduler(std::shared_ptr<scheduler::RefreshRateSelector>);
 
+    auto& dmc() { return mFlinger.mutableDisplayModeController(); }
+
     sp<DisplayDevice> mDisplay, mOuterDisplay;
+    PhysicalDisplayId mDisplayId;
+
     mock::EventThread* mAppEventThread;
 
     static constexpr DisplayModeId kModeId60{0};
@@ -137,16 +187,6 @@
     mAppEventThread = eventThread.get();
     auto sfEventThread = std::make_unique<mock::EventThread>();
 
-    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
@@ -164,134 +204,143 @@
                             TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp);
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateWithRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
-
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
 
     mFlinger.commit();
-
     Mock::VerifyAndClearExpectations(mComposer);
 
-    EXPECT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that the next commit will complete the mode change and send
     // a onModeChanged event to the framework.
 
     EXPECT_CALL(*mAppEventThread,
                 onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
+
     mFlinger.commit();
     Mock::VerifyAndClearExpectations(mAppEventThread);
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
-TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateWithoutRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
+    constexpr bool kAllowGroupSwitching = true;
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(
+                      mDisplay->getDisplayToken().promote(),
+                      mock::createDisplayModeSpecs(kModeId90, 120_Hz, kAllowGroupSwitching)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, true, 0, 120));
-
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
 
     EXPECT_CALL(*mAppEventThread,
                 onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
 
     mFlinger.commit();
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
-TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
-    ftl::FakeGuard guard(kMainThreadContext);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnTwoDisplaysWithoutRefreshRequired) {
+    const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
-    // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
-    // is still being processed the later call will be respected.
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz,
+                                                                               true)));
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId60, 60_Hz,
+                                                                               true)));
 
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90, false, 0, 120));
-
-    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90);
-
-    mFlinger.commit();
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId120, false, 0, 180));
-
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
-
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId120);
-
-    mFlinger.commit();
-
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
-
-    mFlinger.commit();
-
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
-}
-
-TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
-
-    mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
-
-    mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                        mock::createDisplayModeSpecs(kModeId90_4K, false, 0, 120));
-
-    ASSERT_TRUE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K);
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     // Verify that next commit will call setActiveConfigWithConstraints in HWC
     // and complete the mode change.
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
-    EXPECT_SET_ACTIVE_CONFIG(PrimaryDisplayVariant::HWC_DISPLAY_ID, kModeId90_4K);
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
-    EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplay->getPhysicalId(), true));
+    EXPECT_CALL(*mAppEventThread, onModeChanged(_)).Times(2);
 
-    // Misc expecations. We don't need to enforce these method calls, but since the helper methods
-    // already set expectations we should add new ones here, otherwise the test will fail.
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+}
+
+TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
+    // Test that if we call setDesiredDisplayModeSpecs while a previous mode change
+    // is still being processed the later call will be respected.
+
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
+
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
+
+    mFlinger.commit();
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId120,
+                                                                               180_Hz)));
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId120);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId120));
+}
+
+TEST_F(DisplayModeSwitchingTest, changeResolutionWithoutRefreshRequired) {
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId90_4K,
+                                                                               120_Hz)));
+
+    EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90_4K));
+
+    // Verify that next commit will call setActiveConfigWithConstraints in HWC
+    // and complete the mode change.
+    const VsyncPeriodChangeTimeline timeline{.refreshRequired = false};
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90_4K);
+
+    EXPECT_CALL(*mAppEventThread, onHotplugReceived(mDisplayId, true));
+
+    // Override expectations set up by PrimaryDisplayVariant.
     EXPECT_CALL(*mConsumer,
                 setDefaultBufferSize(static_cast<uint32_t>(kResolution4K.getWidth()),
                                      static_cast<uint32_t>(kResolution4K.getHeight())))
@@ -304,156 +353,101 @@
     injectFakeNativeWindowSurfaceFactory();
     PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
 
-    const auto displayToken = mDisplay->getDisplayToken().promote();
-
     mFlinger.commit();
 
-    // The DisplayDevice will be destroyed and recreated,
-    // so we need to update with the new instance.
-    mDisplay = mFlinger.getDisplay(displayToken);
-
-    EXPECT_FALSE(mDisplay->getDesiredMode());
-    EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
-}
-
-MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
-    if (!arg->getDesiredMode()) {
-        *result_listener << "No desired mode";
-        return false;
-    }
-
-    if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) {
-        *result_listener << "Unexpected desired mode " << ftl::to_underlying(modeId);
-        return false;
-    }
-
-    if (!flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
-        *result_listener << "VsyncModulator did not shift to early phase";
-        return false;
-    }
-
-    return true;
-}
-
-MATCHER_P(ModeSettledTo, modeId, "") {
-    if (const auto desiredOpt = arg->getDesiredMode()) {
-        *result_listener << "Unsettled desired mode "
-                         << ftl::to_underlying(desiredOpt->mode.modePtr->getId());
-        return false;
-    }
-
-    ftl::FakeGuard guard(kMainThreadContext);
-
-    if (arg->getActiveMode().modePtr->getId() != modeId) {
-        *result_listener << "Settled to unexpected active mode " << ftl::to_underlying(modeId);
-        return false;
-    }
-
-    return true;
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90_4K));
 }
 
 TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
-
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
-    // Only the inner display is powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
     EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    innerDisplay->setPowerMode(hal::PowerMode::OFF);
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Only the outer display is powered on.
-    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
-
     EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
+
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+    EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId60);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 }
 
 TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
-    // Both displays are powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
@@ -469,23 +463,22 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) {
     EXPECT_TRUE(mDisplay->isPoweredOn());
-    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId60));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
 
     // Power off the display before the mode has been set.
-    mDisplay->setPowerMode(hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(mDisplay, hal::PowerMode::OFF);
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
     EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
@@ -498,81 +491,74 @@
 
     mFlinger.commit();
 
-    EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90));
+    EXPECT_THAT(mDisplay, ModeSettledTo(&dmc(), kModeId90));
 }
 
 TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
     SET_FLAG_FOR_TEST(flags::connected_display, true);
 
-    // For the inner display, this is handled by setupHwcHotplugCallExpectations.
-    EXPECT_CALL(*mComposer, getDisplayConnectionType(kOuterDisplayHwcId, _))
-            .WillOnce(DoAll(SetArgPointee<1>(IComposerClient::DisplayConnectionType::INTERNAL),
-                            Return(hal::V2_4::Error::NONE)));
-
     const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
 
     EXPECT_TRUE(innerDisplay->isPoweredOn());
     EXPECT_FALSE(outerDisplay->isPoweredOn());
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::ON);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
 
-    // Both displays are powered on.
-    mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId60));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId90, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId90, 120_Hz)));
 
     EXPECT_EQ(NO_ERROR,
               mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
-                                                  mock::createDisplayModeSpecs(kModeId60, false,
-                                                                               0.f, 120.f)));
+                                                  mock::createDisplayModeSpecs(kModeId60, 120_Hz)));
 
     EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     // Power off the outer display before the mode has been set.
-    outerDisplay->setPowerMode(hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::OFF);
 
     const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
     EXPECT_SET_ACTIVE_CONFIG(kInnerDisplayHwcId, kModeId90);
-
-    mFlinger.commit();
-
-    // Powering off the inactive display should abort the mode set.
-    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    mFlinger.commit();
-
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
-
-    innerDisplay->setPowerMode(hal::PowerMode::OFF);
-    outerDisplay->setPowerMode(hal::PowerMode::ON);
-
-    // Only the outer display is powered on.
-    mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
-
     EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId60);
 
     mFlinger.commit();
 
-    // The mode set should resume once the display becomes active.
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+    // Powering off the inactive display should not abort the mode set.
+    EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
     EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
 
     mFlinger.commit();
 
-    EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
-    EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId60));
+
+    mFlinger.setPowerModeInternal(innerDisplay, hal::PowerMode::OFF);
+    mFlinger.setPowerModeInternal(outerDisplay, hal::PowerMode::ON);
+
+    EXPECT_EQ(NO_ERROR,
+              mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+                                                  mock::createDisplayModeSpecs(kModeId120,
+                                                                               120_Hz)));
+
+    EXPECT_SET_ACTIVE_CONFIG(kOuterDisplayHwcId, kModeId120);
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId120));
+
+    mFlinger.commit();
+
+    EXPECT_THAT(innerDisplay, ModeSettledTo(&dmc(), kModeId90));
+    EXPECT_THAT(outerDisplay, ModeSettledTo(&dmc(), kModeId120));
 }
 
 } // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index b620830..9bf344c 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -265,6 +265,13 @@
     processesHotplugConnectCommon<SimpleExternalDisplayCase>();
 }
 
+TEST_F(DisplayTransactionCommitTest, processesHotplugConnectNonSecureExternalDisplay) {
+    // Inject a primary display.
+    PrimaryDisplayVariant::injectHwcDisplay(this);
+
+    processesHotplugConnectCommon<SimpleExternalDisplayNonSecureCase>();
+}
+
 TEST_F(DisplayTransactionCommitTest, ignoresHotplugConnectIfPrimaryAndExternalAlreadyConnected) {
     // Inject both a primary and external display.
     PrimaryDisplayVariant::injectHwcDisplay(this);
@@ -273,13 +280,29 @@
     // TODO: This is an unnecessary call.
     EXPECT_CALL(*mComposer,
                 getDisplayIdentificationData(TertiaryDisplayVariant::HWC_DISPLAY_ID, _, _))
-            .WillOnce(DoAll(SetArgPointee<1>(TertiaryDisplay::PORT),
-                            SetArgPointee<2>(TertiaryDisplay::GET_IDENTIFICATION_DATA()),
+            .WillOnce(DoAll(SetArgPointee<1>(TertiaryDisplay<kSecure>::PORT),
+                            SetArgPointee<2>(TertiaryDisplay<kSecure>::GET_IDENTIFICATION_DATA()),
                             Return(Error::NONE)));
 
     ignoresHotplugConnectCommon<SimpleTertiaryDisplayCase>();
 }
 
+TEST_F(DisplayTransactionCommitTest,
+       ignoresHotplugConnectNonSecureIfPrimaryAndExternalAlreadyConnected) {
+    // Inject both a primary and external display.
+    PrimaryDisplayVariant::injectHwcDisplay(this);
+    ExternalDisplayVariant::injectHwcDisplay(this);
+
+    // TODO: This is an unnecessary call.
+    EXPECT_CALL(*mComposer,
+                getDisplayIdentificationData(TertiaryDisplayVariant::HWC_DISPLAY_ID, _, _))
+            .WillOnce(DoAll(SetArgPointee<1>(TertiaryDisplay<kSecure>::PORT),
+                            SetArgPointee<2>(TertiaryDisplay<kSecure>::GET_IDENTIFICATION_DATA()),
+                            Return(Error::NONE)));
+
+    ignoresHotplugConnectCommon<SimpleTertiaryDisplayNonSecureCase>();
+}
+
 TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectPrimaryDisplay) {
     EXPECT_EXIT(processesHotplugDisconnectCommon<SimplePrimaryDisplayCase>(),
                 testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
@@ -289,6 +312,10 @@
     processesHotplugDisconnectCommon<SimpleExternalDisplayCase>();
 }
 
+TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectNonSecureExternalDisplay) {
+    processesHotplugDisconnectCommon<SimpleExternalDisplayNonSecureCase>();
+}
+
 TEST_F(DisplayTransactionCommitTest, processesHotplugConnectThenDisconnectPrimary) {
     EXPECT_EXIT(
             [this] {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
index 4e9fba7..f424133 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
@@ -42,8 +42,8 @@
 }
 
 TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) {
-    const String8 displayName("fakeDisplay");
-    sp<IBinder> displayToken = mFlinger.createDisplay(displayName, false);
+    static const std::string kDisplayName("fakeDisplay");
+    sp<IBinder> displayToken = mFlinger.createVirtualDisplay(kDisplayName, false /*isSecure*/);
     DisplayStatInfo info;
     status_t status = mFlinger.getDisplayStats(displayToken, &info);
     EXPECT_EQ(status, NAME_NOT_FOUND);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
index db6df22..4bc134f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
@@ -18,7 +18,7 @@
 #define LOG_TAG "LibSurfaceFlingerUnittests"
 
 #include <gtest/gtest.h>
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <private/gui/ComposerService.h>
 #include <private/gui/ComposerServiceAIDL.h>
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index 897f9a0..aef467a 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -48,7 +48,7 @@
 
 TEST_F(HotplugTest, schedulesFrameToCommitDisplayTransaction) {
     EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(1);
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     constexpr HWDisplayId displayId1 = 456;
     mFlinger.onComposerHalHotplugEvent(displayId1, DisplayHotplugEvent::DISCONNECTED);
@@ -73,7 +73,7 @@
             .WillOnce(Return(Error::NONE));
 
     // A single commit should be scheduled for both configure calls.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
     mFlinger.configure();
@@ -116,7 +116,7 @@
                 setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
             .WillOnce(Return(Error::NONE));
 
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
     mFlinger.configure();
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index eaf4684..5231965 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -28,7 +28,7 @@
 
 TEST_F(InitializeDisplaysTest, initializesDisplays) {
     // Scheduled by the display transaction, and by powering on each display.
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(3);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(3);
 
     EXPECT_CALL(static_cast<mock::VSyncTracker&>(
                         mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 83e2f98..fed7b2e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -271,7 +271,7 @@
     }
 
     static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
-        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame(_)).Times(1);
     }
 
     static void setupComposerCallExpectations(DisplayTransactionTest* test, PowerMode mode) {
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index c0796df..352000e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -232,12 +232,14 @@
     // Invocation
 
     DisplayDeviceState state;
-    if constexpr (constexpr auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+
+    constexpr auto kConnectionTypeOpt = Case::Display::CONNECTION_TYPE::value;
+    if constexpr (kConnectionTypeOpt) {
         const auto displayId = PhysicalDisplayId::tryCast(Case::Display::DISPLAY_ID::get());
         ASSERT_TRUE(displayId);
         const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
         ASSERT_TRUE(hwcDisplayId);
-        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId);
+        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId, std::nullopt);
         DisplayModePtr activeMode = DisplayMode::Builder(Case::Display::HWC_ACTIVE_CONFIG_ID)
                                             .setResolution(Case::Display::RESOLUTION)
                                             .setVsyncPeriod(DEFAULT_VSYNC_PERIOD)
@@ -255,10 +257,15 @@
             colorModes.push_back(ColorMode::DISPLAY_P3);
         }
 
-        mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId,
-                                                              *connectionType,
-                                                              makeModes(activeMode),
-                                                              std::move(colorModes), std::nullopt);
+        const auto it = mFlinger.mutablePhysicalDisplays()
+                                .emplace_or_replace(*displayId, displayToken, *displayId,
+                                                    *kConnectionTypeOpt, makeModes(activeMode),
+                                                    std::move(colorModes), std::nullopt)
+                                .first;
+
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       mFlinger.mutableDisplayModeController()
+                               .registerDisplay(it->second.snapshot(), activeMode->getId(), {}));
     }
 
     state.isSecure = static_cast<bool>(Case::Display::SECURE);
@@ -286,9 +293,12 @@
     EXPECT_EQ(Case::Display::DISPLAY_FLAGS & DisplayDevice::eReceivesInput,
               device->receivesInput());
 
-    if constexpr (Case::Display::CONNECTION_TYPE::value) {
+    if constexpr (kConnectionTypeOpt) {
         ftl::FakeGuard guard(kMainThreadContext);
-        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId());
+        EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID,
+                  mFlinger.mutableDisplayModeController()
+                          .getActiveMode(device->getPhysicalId())
+                          .modePtr->getHwcId());
     }
 }
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
deleted file mode 100644
index 0e5f1ea..0000000
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
+++ /dev/null
@@ -1,194 +0,0 @@
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/LayerMetadata.h>
-
-#include "TestableSurfaceFlinger.h"
-
-namespace android {
-
-using testing::_;
-using testing::Return;
-
-class SurfaceFlingerUpdateLayerMetadataSnapshotTest : public testing::Test {
-public:
-    SurfaceFlingerUpdateLayerMetadataSnapshotTest() { mFlinger.setupMockScheduler(); }
-
-protected:
-    sp<Layer> createLayer(const char* name, LayerMetadata& inOutlayerMetadata) {
-        LayerCreationArgs args =
-                LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata};
-        inOutlayerMetadata = args.metadata;
-        return sp<Layer>::make(args);
-    }
-
-    TestableSurfaceFlinger mFlinger;
-};
-
-class LayerMetadataBuilder {
-public:
-    LayerMetadataBuilder(LayerMetadata layerMetadata = {}) : mLayerMetadata(layerMetadata) {}
-
-    LayerMetadataBuilder& setInt32(uint32_t key, int32_t value) {
-        mLayerMetadata.setInt32(key, value);
-        return *this;
-    }
-
-    LayerMetadata build() { return mLayerMetadata; }
-
-private:
-    LayerMetadata mLayerMetadata;
-};
-
-bool operator==(const LayerMetadata& lhs, const LayerMetadata& rhs) {
-    return lhs.mMap == rhs.mMap;
-}
-
-std::ostream& operator<<(std::ostream& stream, const LayerMetadata& layerMetadata) {
-    stream << "LayerMetadata{";
-    for (auto it = layerMetadata.mMap.cbegin(); it != layerMetadata.mMap.cend(); it++) {
-        if (it != layerMetadata.mMap.cbegin()) {
-            stream << ", ";
-        }
-        stream << layerMetadata.itemToString(it->first, ":");
-    }
-    return stream << "}";
-}
-
-// Test that the snapshot's layer metadata is set.
-TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesSnapshotMetadata) {
-    auto layerMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build();
-    auto layer = createLayer("layer", layerMetadata);
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
-
-    mFlinger.updateLayerMetadataSnapshot();
-
-    EXPECT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata);
-}
-
-// Test that snapshot layer metadata is set by merging the child's metadata on top of its
-// parent's metadata.
-TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, mergesSnapshotMetadata) {
-    auto layerAMetadata = LayerMetadataBuilder()
-                                  .setInt32(METADATA_OWNER_UID, 1)
-                                  .setInt32(METADATA_TASK_ID, 2)
-                                  .build();
-    auto layerA = createLayer("parent", layerAMetadata);
-    auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 3).build();
-    auto layerB = createLayer("child", layerBMetadata);
-    layerA->addChild(layerB);
-    layerA->commitChildList();
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
-
-    mFlinger.updateLayerMetadataSnapshot();
-
-    EXPECT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata);
-    auto expectedChildMetadata =
-            LayerMetadataBuilder(layerAMetadata).setInt32(METADATA_TASK_ID, 3).build();
-    EXPECT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata);
-}
-
-// Test that snapshot relative layer metadata is set to the parent's layer metadata merged on top of
-// that parent's relative layer metadata.
-TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadata) {
-    auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build();
-    auto layerA = createLayer("relative-parent", layerAMetadata);
-    auto layerAHandle = layerA->getHandle();
-    auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 2).build();
-    auto layerB = createLayer("relative-child", layerBMetadata);
-    layerB->setRelativeLayer(layerAHandle, 1);
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerB);
-
-    mFlinger.updateLayerMetadataSnapshot();
-
-    EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
-    EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
-}
-
-// Test that snapshot relative layer metadata is set correctly when a layer is interleaved within
-// two other layers.
-//
-// Layer
-//      A
-//     / \
-//    B   D
-//   /
-//  C
-//
-// Z-order Relatives
-//    B <- D <- C
-TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInterleaved) {
-    auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build();
-    auto layerA = createLayer("layer-a", layerAMetadata);
-    auto layerBMetadata = LayerMetadataBuilder()
-                                  .setInt32(METADATA_TASK_ID, 2)
-                                  .setInt32(METADATA_OWNER_PID, 3)
-                                  .build();
-    auto layerB = createLayer("layer-b", layerBMetadata);
-    auto layerBHandle = layerB->getHandle();
-    LayerMetadata layerCMetadata;
-    auto layerC = createLayer("layer-c", layerCMetadata);
-    auto layerDMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 4).build();
-    auto layerD = createLayer("layer-d", layerDMetadata);
-    auto layerDHandle = layerD->getHandle();
-    layerB->addChild(layerC);
-    layerA->addChild(layerB);
-    layerA->addChild(layerD);
-    layerC->setRelativeLayer(layerDHandle, 1);
-    layerD->setRelativeLayer(layerBHandle, 1);
-    layerA->commitChildList();
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
-
-    mFlinger.updateLayerMetadataSnapshot();
-
-    auto expectedLayerDRelativeMetadata =
-            LayerMetadataBuilder()
-                    // From layer A, parent of relative parent
-                    .setInt32(METADATA_OWNER_UID, 1)
-                    // From layer B, relative parent
-                    .setInt32(METADATA_TASK_ID, 2)
-                    .setInt32(METADATA_OWNER_PID, 3)
-                    // added by layer creation args
-                    .setInt32(gui::METADATA_CALLING_UID,
-                              layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
-                    .build();
-    EXPECT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata);
-    auto expectedLayerCRelativeMetadata =
-            LayerMetadataBuilder()
-                    // From layer A, parent of relative parent
-                    .setInt32(METADATA_OWNER_UID, 1)
-                    // From layer B, relative parent of relative parent
-                    .setInt32(METADATA_OWNER_PID, 3)
-                    // From layer D, relative parent
-                    .setInt32(METADATA_TASK_ID, 4)
-                    // added by layer creation args
-                    .setInt32(gui::METADATA_CALLING_UID,
-                              layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
-                    .build();
-    EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata);
-}
-
-TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest,
-       updatesRelativeMetadataMultipleRelativeChildren) {
-    auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build();
-    auto layerA = createLayer("layer-a", layerAMetadata);
-    auto layerAHandle = layerA->getHandle();
-    LayerMetadata layerBMetadata;
-    auto layerB = createLayer("layer-b", layerBMetadata);
-    LayerMetadata layerCMetadata;
-    auto layerC = createLayer("layer-c", layerCMetadata);
-    layerB->setRelativeLayer(layerAHandle, 1);
-    layerC->setRelativeLayer(layerAHandle, 2);
-    layerA->commitChildList();
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerB);
-    mFlinger.mutableDrawingState().layersSortedByZ.add(layerC);
-
-    mFlinger.updateLayerMetadataSnapshot();
-
-    EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
-    EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
-    EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 1e02c67..9de3346 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -53,7 +53,7 @@
                       factory, selectorPtr->getActiveMode().fps, timeStats) {
         const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
         registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
-                        std::move(tracker));
+                        std::move(tracker), displayId);
 
         ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
             // Execute task to prevent broken promise exception on destruction.
@@ -62,7 +62,7 @@
     }
 
     MOCK_METHOD(void, scheduleConfigure, (), (override));
-    MOCK_METHOD(void, scheduleFrame, (), (override));
+    MOCK_METHOD(void, scheduleFrame, (Duration), (override));
     MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
 
     void doFrameSignal(ICompositor& compositor, VsyncId vsyncId) {
@@ -74,10 +74,8 @@
     void setEventThread(Cycle cycle, std::unique_ptr<EventThread> eventThreadPtr) {
         if (cycle == Cycle::Render) {
             mRenderEventThread = std::move(eventThreadPtr);
-            mRenderEventConnection = mRenderEventThread->createEventConnection();
         } else {
             mLastCompositeEventThread = std::move(eventThreadPtr);
-            mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
         }
     }
 
@@ -85,14 +83,16 @@
 
     void registerDisplay(
             PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+            std::optional<PhysicalDisplayId> activeDisplayIdOpt = {},
             std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
         registerDisplay(displayId, std::move(selectorPtr),
-                        std::make_unique<mock::VsyncController>(), vsyncTracker);
+                        std::make_unique<mock::VsyncController>(), vsyncTracker,
+                        activeDisplayIdOpt.value_or(displayId));
     }
 
     void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
                          std::unique_ptr<VsyncController> controller,
-                         std::shared_ptr<VSyncTracker> tracker) {
+                         std::shared_ptr<VSyncTracker> tracker, PhysicalDisplayId activeDisplayId) {
         ftl::FakeGuard guard(kMainThreadContext);
         Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
                                            std::shared_ptr<VsyncSchedule>(
@@ -101,14 +101,15 @@
                                                                              mock::VSyncDispatch>(),
                                                                      std::move(controller),
                                                                      mockRequestHardwareVsync
-                                                                             .AsStdFunction())));
+                                                                             .AsStdFunction())),
+                                           activeDisplayId);
     }
 
     testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
 
-    void unregisterDisplay(PhysicalDisplayId displayId) {
+    void setDisplayPowerMode(PhysicalDisplayId displayId, hal::PowerMode powerMode) {
         ftl::FakeGuard guard(kMainThreadContext);
-        Scheduler::unregisterDisplay(displayId);
+        Scheduler::setDisplayPowerMode(displayId, powerMode);
     }
 
     std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
@@ -130,7 +131,9 @@
     using Scheduler::resyncAllToHardwareVsync;
 
     auto& mutableLayerHistory() { return mLayerHistory; }
-    auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
+    auto& mutableAttachedChoreographers() NO_THREAD_SAFETY_ANALYSIS {
+        return mAttachedChoreographers;
+    }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
@@ -173,6 +176,11 @@
         mPolicy.idleTimer = globalSignals.idle ? TimerState::Expired : TimerState::Reset;
     }
 
+    using Scheduler::TimerState;
+
+    using Scheduler::idleTimerCallback;
+    using Scheduler::touchTimerCallback;
+
     void setContentRequirements(std::vector<RefreshRateSelector::LayerRequirement> layers) {
         std::lock_guard<std::mutex> lock(mPolicyLock);
         mPolicy.contentRequirements = std::move(layers);
@@ -185,15 +193,7 @@
         return Scheduler::chooseDisplayModes();
     }
 
-    void dispatchCachedReportedMode() {
-        std::lock_guard<std::mutex> lock(mPolicyLock);
-        Scheduler::dispatchCachedReportedMode();
-    }
-
-    void clearCachedReportedMode() {
-        std::lock_guard<std::mutex> lock(mPolicyLock);
-        mPolicy.cachedModeChangedParams.reset();
-    }
+    using Scheduler::onDisplayModeChanged;
 
     void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) {
         auto schedule = getVsyncSchedule(id);
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index bce7729..c043b88 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -16,12 +16,13 @@
 
 #pragma once
 
-#include <algorithm>
 #include <chrono>
+#include <memory>
 #include <variant>
 
 #include <ftl/fake_guard.h>
 #include <ftl/match.h>
+#include <gui/LayerMetadata.h>
 #include <gui/ScreenCaptureResults.h>
 #include <ui/DynamicDisplayInfo.h>
 
@@ -38,14 +39,14 @@
 #include "FrameTracer/FrameTracer.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerHandle.h"
+#include "FrontEnd/RequestedLayerState.h"
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/RefreshRateSelector.h"
-#include "StartPropertySetThread.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
+#include "android/gui/ISurfaceComposerClient.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockDisplayMode.h"
 #include "mock/DisplayHardware/MockPowerAdvisor.h"
@@ -57,7 +58,6 @@
 
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncController.h"
-#include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
@@ -85,19 +85,13 @@
 public:
     ~Factory() = default;
 
-    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override {
-        return nullptr;
-    }
+    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
 
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps /*currentRefreshRate*/) override {
         return std::make_unique<scheduler::FakePhaseOffsets>();
     }
 
-    sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
-        return sp<StartPropertySetThread>::make(timestampPropertyValue);
-    }
-
     sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) override {
         return sp<DisplayDevice>::make(creationArgs);
     }
@@ -132,7 +126,7 @@
 
     sp<Layer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
 
-    sp<LayerFE> createLayerFE(const std::string& layerName) override {
+    sp<LayerFE> createLayerFE(const std::string& layerName, const Layer* /* owner */) override {
         return sp<LayerFE>::make(layerName);
     }
 
@@ -192,6 +186,8 @@
     void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
         mFlinger->mCompositionEngine->setHwComposer(
                 std::make_unique<impl::HWComposer>(std::move(composer)));
+        mFlinger->mDisplayModeController.setHwComposer(
+                &mFlinger->mCompositionEngine->getHwComposer());
     }
 
     void setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor> powerAdvisor) {
@@ -202,6 +198,11 @@
         mFlinger->mCompositionEngine->setTimeStats(timeStats);
     }
 
+    void setupCompositionEngine(
+            std::unique_ptr<compositionengine::CompositionEngine> compositionEngine) {
+        mFlinger->mCompositionEngine = std::move(compositionEngine);
+    }
+
     enum class SchedulerCallbackImpl { kNoOp, kMock };
 
     struct DefaultDisplayMode {
@@ -270,17 +271,6 @@
 
         auto eventThread = makeMock<mock::EventThread>(options.useNiceMock);
         auto sfEventThread = makeMock<mock::EventThread>(options.useNiceMock);
-
-        EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*eventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
-        EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
@@ -320,35 +310,25 @@
 
     auto& mutableStateLock() { return mFlinger->mStateLock; }
 
-    static auto findOutputLayerForDisplay(const sp<Layer>& layer,
-                                          const sp<const DisplayDevice>& display) {
-        return layer->findOutputLayerForDisplay(display.get());
-    }
-
-    static void setLayerSidebandStream(const sp<Layer>& layer,
-                                       const sp<NativeHandle>& sidebandStream) {
-        layer->mDrawingState.sidebandStream = sidebandStream;
-        layer->mSidebandStream = sidebandStream;
-        layer->editLayerSnapshot()->sidebandStream = sidebandStream;
+    compositionengine::OutputLayer* findOutputLayerForDisplay(
+            uint32_t layerId, const sp<const DisplayDevice>& display) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        if (mFlinger->mLegacyLayers.find(layerId) == mFlinger->mLegacyLayers.end()) {
+            return nullptr;
+        }
+        return mFlinger->mLegacyLayers[layerId]->findOutputLayerForDisplay(display.get());
     }
 
     void setLayerCompositionType(const sp<Layer>& layer,
                                  aidl::android::hardware::graphics::composer3::Composition type) {
-        auto outputLayer = findOutputLayerForDisplay(layer, mFlinger->getDefaultDisplayDevice());
+        auto outputLayer = findOutputLayerForDisplay(static_cast<uint32_t>(layer->sequence),
+                                                     mFlinger->getDefaultDisplayDevice());
         LOG_ALWAYS_FATAL_IF(!outputLayer);
         auto& state = outputLayer->editState();
         LOG_ALWAYS_FATAL_IF(!outputLayer->getState().hwc);
         (*state.hwc).hwcCompositionType = type;
     }
 
-    static void setLayerPotentialCursor(const sp<Layer>& layer, bool potentialCursor) {
-        layer->mPotentialCursor = potentialCursor;
-    }
-
-    static void setLayerDrawingParent(const sp<Layer>& layer, const sp<Layer>& drawingParent) {
-        layer->mDrawingParent = drawingParent;
-    }
-
     /* ------------------------------------------------------------------------
      * Forwarding for functions being tested
      */
@@ -389,7 +369,7 @@
             targets.try_emplace(id, &frameTargeter.target());
             targeters.try_emplace(id, &frameTargeter);
         }
-
+        mFlinger->setTransactionFlags(eTransactionFlushNeeded);
         mFlinger->commit(displayId, targets);
 
         if (composite) {
@@ -416,12 +396,21 @@
         commit(kComposite);
     }
 
-    auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) {
-        return mFlinger->createDisplay(displayName, secure, requestedRefreshRate);
+    auto createVirtualDisplay(const std::string& displayName, bool isSecure,
+                              float requestedRefreshRate = 0.0f) {
+        static const std::string kTestId =
+                "virtual:libsurfaceflinger_unittest:TestableSurfaceFlinger";
+        return mFlinger->createVirtualDisplay(displayName, isSecure, kTestId, requestedRefreshRate);
     }
 
-    auto destroyDisplay(const sp<IBinder>& displayToken) {
-        return mFlinger->destroyDisplay(displayToken);
+    auto createVirtualDisplay(const std::string& displayName, bool isSecure,
+                              const std::string& uniqueId, float requestedRefreshRate = 0.0f) {
+        return mFlinger->createVirtualDisplay(displayName, isSecure, uniqueId,
+                                              requestedRefreshRate);
+    }
+
+    auto destroyVirtualDisplay(const sp<IBinder>& displayToken) {
+        return mFlinger->destroyVirtualDisplay(displayToken);
     }
 
     auto getDisplay(const sp<IBinder>& displayToken) {
@@ -444,6 +433,7 @@
     void commitTransactionsLocked(uint32_t transactionFlags) {
         Mutex::Autolock lock(mFlinger->mStateLock);
         ftl::FakeGuard guard(kMainThreadContext);
+        mFlinger->processDisplayChangesLocked();
         mFlinger->commitTransactionsLocked(transactionFlags);
     }
 
@@ -471,41 +461,50 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
-                          SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
+    auto renderScreenImpl(const sp<DisplayDevice> display,
+                          std::unique_ptr<const RenderArea> renderArea,
+                          SurfaceFlinger::GetLayerSnapshotsFunction getLayerSnapshotsFn,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                           bool regionSampling) {
+        Mutex::Autolock lock(mFlinger->mStateLock);
+        ftl::FakeGuard guard(kMainThreadContext);
+
         ScreenCaptureResults captureResults;
-        return FTL_FAKE_GUARD(kMainThreadContext,
-                              mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
-                                                         buffer, regionSampling,
-                                                         false /* grayscale */,
-                                                         false /* isProtected */, captureResults));
+        auto displayState = std::optional{display->getCompositionDisplay()->getState()};
+        auto layers = getLayerSnapshotsFn();
+        auto layerFEs = mFlinger->extractLayerFEs(layers);
+
+        return mFlinger->renderScreenImpl(renderArea.get(), buffer, regionSampling,
+                                          false /* grayscale */, false /* isProtected */,
+                                          false /* attachGainmap */, captureResults, displayState,
+                                          layers, layerFEs);
     }
 
-    auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
-                                    std::unordered_set<uint32_t> excludeLayerIds,
-                                    const LayerVector::Visitor& visitor) {
-        return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid,
-                                                                    excludeLayerIds, visitor);
+    auto getLayerSnapshotsForScreenshotsFn(ui::LayerStack layerStack, uint32_t uid) {
+        return mFlinger->getLayerSnapshotsForScreenshots(layerStack, uid,
+                                                         std::unordered_set<uint32_t>{});
     }
 
     auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
-                                   ui::DisplayPrimaries &primaries) {
+                                   ui::DisplayPrimaries& primaries) {
         return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
     }
 
-    auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
-    auto& getPendingTransactionQueue() {
+    auto& getTransactionQueue() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mTransactionHandler.mLocklessTransactionQueue;
+    }
+    auto& getPendingTransactionQueue() NO_THREAD_SAFETY_ANALYSIS {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
     size_t getPendingTransactionCount() {
+        ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
     }
 
     auto setTransactionState(
             const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
-            const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+            Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
             const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
             bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
             bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
@@ -518,11 +517,13 @@
     }
 
     auto setTransactionStateInternal(TransactionState& transaction) {
-        return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction));
+        return FTL_FAKE_GUARD(kMainThreadContext,
+                              mFlinger->mTransactionHandler.queueTransaction(
+                                      std::move(transaction)));
     }
 
     auto flushTransactionQueues() {
-        return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->flushTransactionQueues(kVsyncId));
+        return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->flushTransactionQueues());
     }
 
     auto onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
@@ -559,8 +560,6 @@
         return mFlinger->mirrorLayer(args, mirrorFromHandle, outResult);
     }
 
-    void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); }
-
     void getDynamicDisplayInfoFromToken(const sp<IBinder>& displayToken,
                                         ui::DynamicDisplayInfo* dynamicDisplayInfo) {
         mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo);
@@ -588,6 +587,25 @@
         return mFlinger->getDisplayStats(displayToken, outInfo);
     }
 
+    // Used to add a layer before updateLayerSnapshots is called.
+    // Must have transactionsFlushed enabled for the new layer to be updated.
+    void addLayer(std::unique_ptr<frontend::RequestedLayerState>& layer) {
+        std::scoped_lock<std::mutex> lock(mFlinger->mCreatedLayersLock);
+        mFlinger->mNewLayers.emplace_back(std::move(layer));
+    }
+
+    // Used to add a layer before updateLayerSnapshots is called.
+    // Must have transactionsFlushed enabled for the new layer to be updated.
+    void addLayer(uint32_t layerId) {
+        std::scoped_lock<std::mutex> lock(mFlinger->mCreatedLayersLock);
+        LayerCreationArgs args(std::make_optional(layerId));
+        args.flinger = this->mFlinger.get();
+        auto layer = std::make_unique<frontend::RequestedLayerState>(args);
+        auto legacyLayer = sp<Layer>::make(args);
+        injectLegacyLayer(legacyLayer);
+        mFlinger->mNewLayers.emplace_back(std::move(layer));
+    }
+
     /* ------------------------------------------------------------------------
      * Read-only access to private data to assert post-conditions.
      */
@@ -603,15 +621,32 @@
     }
 
     void injectLegacyLayer(sp<Layer> layer) {
-        mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer;
+        FTL_FAKE_GUARD(kMainThreadContext,
+                       mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer);
+    }
+
+    void releaseLegacyLayer(uint32_t sequence) {
+        FTL_FAKE_GUARD(kMainThreadContext, mFlinger->mLegacyLayers.erase(sequence));
+    }
+
+    auto getLegacyLayer(uint32_t layerId) {
+        ftl::FakeGuard guard(kMainThreadContext);
+        return mFlinger->mLegacyLayers[layerId];
     };
 
-    void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
+    void destroyAllLayerHandles() {
+        ftl::FakeGuard guard(kMainThreadContext);
+        for (auto [layerId, legacyLayer] : mFlinger->mLegacyLayers) {
+            mFlinger->onHandleDestroyed(nullptr, legacyLayer, layerId);
+        }
+    }
 
     auto setLayerHistoryDisplayArea(uint32_t displayArea) {
         return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
     };
-    auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+    auto updateLayerHistory(nsecs_t now) {
+        return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->updateLayerHistory(now));
+    };
     auto setDaltonizerType(ColorBlindnessType type) {
         mFlinger->mDaltonizer.setType(type);
         return mFlinger->updateColorMatrixLocked();
@@ -626,8 +661,10 @@
      * post-conditions.
      */
 
-    const auto& displays() const { return mFlinger->mDisplays; }
-    const auto& physicalDisplays() const { return mFlinger->mPhysicalDisplays; }
+    const auto& displays() const NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mDisplays; }
+    const auto& physicalDisplays() const NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPhysicalDisplays;
+    }
     const auto& currentState() const { return mFlinger->mCurrentState; }
     const auto& drawingState() const { return mFlinger->mDrawingState; }
     const auto& transactionFlags() const { return mFlinger->mTransactionFlags; }
@@ -637,15 +674,20 @@
 
     auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; }
 
+    auto& mutableDisplayModeController() { return mFlinger->mDisplayModeController; }
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
-    auto& mutableDisplays() { return mFlinger->mDisplays; }
-    auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; }
+    auto& mutableDisplays() NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mDisplays; }
+    auto& mutablePhysicalDisplays() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPhysicalDisplays;
+    }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
     auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
-    auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
+    auto& mutablePendingHotplugEvents() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPendingHotplugEvents;
+    }
     auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
     auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
     auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }
@@ -653,7 +695,7 @@
     auto& mutableHwcDisplayData() { return getHwComposer().mDisplayData; }
     auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
     auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
-    auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
+    auto& mutableActiveDisplayId() NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mActiveDisplayId; }
     auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; }
 
     auto& mutableActiveDisplayRotationFlags() {
@@ -661,8 +703,9 @@
     }
 
     auto& mutableMinAcquiredBuffers() { return SurfaceFlinger::minAcquiredBuffers; }
-    auto& mutableLayersPendingRemoval() { return mFlinger->mLayersPendingRemoval; }
-    auto& mutableLayerSnapshotBuilder() { return mFlinger->mLayerSnapshotBuilder; };
+    auto& mutableLayerSnapshotBuilder() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mLayerSnapshotBuilder;
+    }
 
     auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
@@ -671,11 +714,6 @@
         return mFlinger->initTransactionTraceWriter();
     }
 
-    void enableNewFrontEnd() {
-        mFlinger->mLayerLifecycleManagerEnabled = true;
-        mFlinger->mLegacyFrontEndEnabled = false;
-    }
-
     void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
                                          TimePoint expectedPresentTime, Fps frameInterval,
                                          std::optional<Period> timeoutOpt) {
@@ -734,7 +772,6 @@
         mutableDisplays().clear();
         mutableCurrentState().displays.clear();
         mutableDrawingState().displays.clear();
-        mFlinger->mLayersPendingRemoval.clear();
         mFlinger->mScheduler.reset();
         mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
         mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
@@ -952,14 +989,14 @@
 
         auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
             mDisplayModes = std::move(modes);
-            mCreationArgs.activeModeId = activeModeId;
+            mActiveModeId = activeModeId;
             mCreationArgs.refreshRateSelector = nullptr;
             return *this;
         }
 
         auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
             mDisplayModes = selectorPtr->displayModes();
-            mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId();
+            mActiveModeId = selectorPtr->getActiveMode().modePtr->getId();
             mCreationArgs.refreshRateSelector = std::move(selectorPtr);
             return *this;
         }
@@ -1001,8 +1038,9 @@
             return *this;
         }
 
-        auto& skipRegisterDisplay() {
-            mRegisterDisplay = false;
+        // Used to avoid overwriting mocks injected by TestableSurfaceFlinger::setupMockScheduler.
+        auto& skipSchedulerRegistration() {
+            mSchedulerRegistration = false;
             return *this;
         }
 
@@ -1015,12 +1053,23 @@
                                  std::shared_ptr<android::scheduler::VSyncTracker> tracker)
                 NO_THREAD_SAFETY_ANALYSIS {
             const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
+            LOG_ALWAYS_FATAL_IF(!displayId);
 
             auto& modes = mDisplayModes;
-            auto& activeModeId = mCreationArgs.activeModeId;
+            auto& activeModeId = mActiveModeId;
 
-            if (displayId && !mCreationArgs.refreshRateSelector) {
-                if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
+            DisplayDeviceState state;
+            state.isSecure = mCreationArgs.isSecure;
+
+            if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
+                LOG_ALWAYS_FATAL_IF(!mConnectionType);
+                LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
+
+                if (mCreationArgs.isPrimary) {
+                    mFlinger.mutableActiveDisplayId() = *physicalId;
+                }
+
+                if (!mCreationArgs.refreshRateSelector) {
                     if (modes.empty()) {
                         constexpr DisplayModeId kModeId{0};
                         DisplayModePtr mode =
@@ -1042,50 +1091,41 @@
                     mCreationArgs.refreshRateSelector =
                             std::make_shared<scheduler::RefreshRateSelector>(modes, activeModeId);
                 }
+
+                const auto activeModeOpt = modes.get(activeModeId);
+                LOG_ALWAYS_FATAL_IF(!activeModeOpt);
+
+                // Save a copy for use after `modes` is consumed.
+                const Fps refreshRate = activeModeOpt->get()->getPeakFps();
+
+                state.physical = {.id = *physicalId,
+                                  .hwcDisplayId = *mHwcDisplayId,
+                                  .activeMode = activeModeOpt->get()};
+
+                const auto it = mFlinger.mutablePhysicalDisplays()
+                                        .emplace_or_replace(*physicalId, mDisplayToken, *physicalId,
+                                                            *mConnectionType, std::move(modes),
+                                                            ui::ColorModes(), std::nullopt)
+                                        .first;
+
+                mFlinger.mutableDisplayModeController()
+                        .registerDisplay(*physicalId, it->second.snapshot(),
+                                         mCreationArgs.refreshRateSelector);
+
+                mFlinger.mutableDisplayModeController().setActiveMode(*physicalId, activeModeId,
+                                                                      refreshRate, refreshRate);
+
+                if (mFlinger.scheduler() && mSchedulerRegistration) {
+                    mFlinger.scheduler()->registerDisplay(*physicalId,
+                                                          mCreationArgs.refreshRateSelector,
+                                                          std::move(controller), std::move(tracker),
+                                                          mFlinger.mutableActiveDisplayId());
+                }
             }
 
             sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
             mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
 
-            DisplayDeviceState state;
-            state.isSecure = mCreationArgs.isSecure;
-
-            if (mConnectionType) {
-                LOG_ALWAYS_FATAL_IF(!displayId);
-                const auto physicalIdOpt = PhysicalDisplayId::tryCast(*displayId);
-                LOG_ALWAYS_FATAL_IF(!physicalIdOpt);
-                const auto physicalId = *physicalIdOpt;
-
-                if (mCreationArgs.isPrimary) {
-                    mFlinger.mutableActiveDisplayId() = physicalId;
-                }
-
-                LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
-
-                const auto activeMode = modes.get(activeModeId);
-                LOG_ALWAYS_FATAL_IF(!activeMode);
-                const auto fps = activeMode->get()->getPeakFps();
-
-                state.physical = {.id = physicalId,
-                                  .hwcDisplayId = *mHwcDisplayId,
-                                  .activeMode = activeMode->get()};
-
-                mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken,
-                                                                      physicalId, *mConnectionType,
-                                                                      std::move(modes),
-                                                                      ui::ColorModes(),
-                                                                      std::nullopt);
-
-                if (mFlinger.scheduler() && mRegisterDisplay) {
-                    mFlinger.scheduler()->registerDisplay(physicalId,
-                                                          display->holdRefreshRateSelector(),
-                                                          std::move(controller),
-                                                          std::move(tracker));
-                }
-
-                display->setActiveMode(activeModeId, fps, fps);
-            }
-
             mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
             mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
 
@@ -1097,7 +1137,8 @@
         sp<BBinder> mDisplayToken = sp<BBinder>::make();
         DisplayDeviceCreationArgs mCreationArgs;
         DisplayModes mDisplayModes;
-        bool mRegisterDisplay = true;
+        DisplayModeId mActiveModeId;
+        bool mSchedulerRegistration = true;
         const std::optional<ui::DisplayConnectionType> mConnectionType;
         const std::optional<hal::HWDisplayId> mHwcDisplayId;
     };
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 1f2a1ed..fab1f6d 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -17,6 +17,8 @@
 #undef LOG_TAG
 #define LOG_TAG "TransactionApplicationTest"
 
+#include <binder/Binder.h>
+#include <common/test/FlagUtils.h>
 #include <compositionengine/Display.h>
 #include <compositionengine/mock/DisplaySurface.h>
 #include <gmock/gmock.h>
@@ -25,17 +27,20 @@
 #include <gui/SurfaceComposerClient.h>
 #include <gui/fake/BufferData.h>
 #include <log/log.h>
+#include <renderengine/mock/RenderEngine.h>
 #include <ui/MockFence.h>
 #include <utils/String8.h>
 #include <vector>
-#include <binder/Binder.h>
 
 #include "FrontEnd/TransactionHandler.h"
 #include "TestableSurfaceFlinger.h"
 #include "TransactionState.h"
 
+#include <com_android_graphics_surfaceflinger_flags.h>
+
 namespace android {
 
+using namespace com::android::graphics::surfaceflinger;
 using testing::_;
 using testing::Return;
 
@@ -51,6 +56,7 @@
 
         mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
         mFlinger.setupMockScheduler();
+        mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
         mFlinger.flinger()->addTransactionReadyFilters();
     }
 
@@ -61,6 +67,7 @@
     }
 
     TestableSurfaceFlinger mFlinger;
+    renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
 
     struct TransactionInfo {
         Vector<ComposerState> states;
@@ -98,7 +105,7 @@
 
     void NotPlacedOnTransactionQueue(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
         TransactionInfo transaction;
         setupSingle(transaction, flags,
                     /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
@@ -122,7 +129,7 @@
 
     void PlaceOnTransactionQueue(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
         // first check will see desired present time has not passed,
         // but afterwards it will look like the desired present time has passed
@@ -148,7 +155,7 @@
     void BlockedByPriorTransaction(uint32_t flags) {
         ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         nsecs_t time = systemTime();
-        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
+        EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(2);
 
         // transaction that should go on the pending thread
         TransactionInfo transactionA;
@@ -210,7 +217,7 @@
 
 TEST_F(TransactionApplicationTest, AddToPendingQueue) {
     ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
@@ -231,7 +238,7 @@
 
 TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
     ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
-    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+    EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame(_)).Times(1);
 
     TransactionInfo transactionA; // transaction to go on pending queue
     setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
@@ -319,15 +326,17 @@
     transaction1.states[0].state.bufferData =
             std::make_shared<fake::BufferData>(/* bufferId */ 1, /* width */ 1, /* height */ 1,
                                                /* pixelFormat */ 0, /* outUsage */ 0);
+    mFlinger.addLayer(1);
+    bool out;
+    mFlinger.updateLayerSnapshots(VsyncId{1}, 0, /* transactionsFlushed */ true, out);
     transaction1.states[0].externalTexture =
             std::make_shared<FakeExternalTexture>(*transaction1.states[0].state.bufferData);
-    transaction1.states[0].state.surface =
-            sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
-                    ->getHandle();
+    transaction1.states[0].state.surface = mFlinger.getLegacyLayer(1)->getHandle();
     auto fence = sp<mock::MockFence>::make();
     EXPECT_CALL(*fence, getStatus()).WillRepeatedly(Return(Fence::Status::Unsignaled));
     transaction1.states[0].state.bufferData->acquireFence = std::move(fence);
     transaction1.states[0].state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
+    transaction1.states[0].layerId = 1;
     transaction1.isAutoTimestamp = true;
 
     // Transaction 2 should be ready to be applied.
@@ -357,8 +366,7 @@
         }
         mFlinger.getPendingTransactionQueue().clear();
         mFlinger.commitTransactionsLocked(eTransactionMask);
-        mFlinger.mutableCurrentState().layersSortedByZ.clear();
-        mFlinger.mutableDrawingState().layersSortedByZ.clear();
+        mFlinger.destroyAllLayerHandles();
     }
 
     static sp<Fence> fence(Fence::Status status) {
@@ -367,8 +375,7 @@
         return fence;
     }
 
-    ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what,
-                                      std::optional<sp<IBinder>> layerHandle = std::nullopt) {
+    ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what) {
         ComposerState state;
         state.state.bufferData =
                 std::make_shared<fake::BufferData>(/* bufferId */ 123L, /* width */ 1,
@@ -376,9 +383,6 @@
                                                    /* outUsage */ 0);
         state.state.bufferData->acquireFence = std::move(fence);
         state.state.layerId = layerId;
-        state.state.surface = layerHandle.value_or(
-                sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
-                        ->getHandle());
         state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
 
         state.state.what = what;
@@ -414,6 +418,19 @@
                               size_t expectedTransactionsPending) {
         EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
         EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size());
+        std::unordered_set<uint32_t> createdLayers;
+        for (auto transaction : transactions) {
+            for (auto& state : transaction.states) {
+                auto layerId = static_cast<uint32_t>(state.state.layerId);
+                if (createdLayers.find(layerId) == createdLayers.end()) {
+                    mFlinger.addLayer(layerId);
+                    createdLayers.insert(layerId);
+                }
+            }
+        }
+        bool unused;
+        bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+                                                           /*transactionsFlushed=*/true, unused);
 
         for (auto transaction : transactions) {
             std::vector<ResolvedComposerState> resolvedStates;
@@ -423,6 +440,9 @@
                 resolvedState.state = std::move(state.state);
                 resolvedState.externalTexture =
                         std::make_shared<FakeExternalTexture>(*resolvedState.state.bufferData);
+                resolvedState.layerId = static_cast<uint32_t>(state.state.layerId);
+                resolvedState.state.surface =
+                        mFlinger.getLegacyLayer(resolvedState.layerId)->getHandle();
                 resolvedStates.emplace_back(resolvedState);
             }
 
@@ -454,9 +474,8 @@
 TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleSignaledFromTheQueue) {
     const sp<IBinder> kApplyToken =
             IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
+    const auto kLayerId = 10;
     const auto kExpectedTransactionsPending = 0u;
-
     const auto signaledTransaction =
             createTransactionInfo(kApplyToken,
                                   {createComposerState(kLayerId, fence(Fence::Status::Signaled),
@@ -498,6 +517,44 @@
     setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
 }
 
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_AutoRefreshChanged) {
+    SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, false);
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsPending = 1u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eAutoRefreshChanged |
+                                                                      layer_state_t::
+                                                                              eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesUnSignaledInTheQueue_AutoRefreshChanged) {
+    SET_FLAG_FOR_TEST(flags::latch_unsignaled_with_auto_refresh_changed, true);
+    const sp<IBinder> kApplyToken =
+            IInterface::asBinder(TransactionCompletedListener::getIInstance());
+    const auto kLayerId = 1;
+    const auto kExpectedTransactionsPending = 0u;
+
+    const auto unsignaledTransaction =
+            createTransactionInfo(kApplyToken,
+                                  {
+                                          createComposerState(kLayerId,
+                                                              fence(Fence::Status::Unsignaled),
+                                                              layer_state_t::eAutoRefreshChanged |
+                                                                      layer_state_t::
+                                                                              eBufferChanged),
+                                  });
+    setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
 TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) {
     const sp<IBinder> kApplyToken =
             IInterface::asBinder(TransactionCompletedListener::getIInstance());
@@ -731,7 +788,7 @@
 TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue) {
     const sp<IBinder> kApplyToken =
             IInterface::asBinder(TransactionCompletedListener::getIInstance());
-    const auto kLayerId = 1;
+    const auto kLayerId = 10;
     const auto kExpectedTransactionsPending = 0u;
 
     const auto signaledTransaction =
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index d4d5b32..abfab9a 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -94,7 +94,7 @@
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
         layer->setBuffer(externalTexture, bufferData, postTime, /*desiredPresentTime*/ 30, false,
-                         dequeueTime, FrameTimelineInfo{});
+                         FrameTimelineInfo{}, gui::GameMode::Unsupported);
 
         commitTransaction(layer.get());
         nsecs_t latchTime = 25;
@@ -112,7 +112,8 @@
         EXPECT_CALL(*mFlinger.getFrameTracer(),
                     traceFence(layerId, bufferId, frameNumber, presentFence,
                                FrameTracer::FrameEvent::PRESENT_FENCE, /*startTime*/ 0));
-        layer->onCompositionPresented(nullptr, glDoneFence, presentFence, compositorTiming);
+        layer->onCompositionPresented(nullptr, glDoneFence, presentFence, compositorTiming,
+                                      gui::GameMode::Unsupported);
     }
 };
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index cbb597a..af02330 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -106,7 +106,7 @@
 
 TEST(TransactionProtoParserTest, parseDisplayInfo) {
     frontend::DisplayInfo d1;
-    d1.info.displayId = 42;
+    d1.info.displayId = ui::LogicalDisplayId{42};
     d1.info.logicalWidth = 43;
     d1.info.logicalHeight = 44;
     d1.info.transform.set(1, 2, 3, 4);
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 5046a86..9a68d75 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -72,7 +72,8 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10,
+                                                             gui::GameMode::Unsupported);
         EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_TRUE(layer->mDrawingState.bufferSurfaceFrameTX == nullptr);
         const auto surfaceFrame = layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*token*/ 1);
@@ -99,7 +100,8 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         acquireFence->signalForTest(12);
 
         commitTransaction(layer.get());
@@ -134,7 +136,8 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -151,7 +154,8 @@
                                                          2ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         nsecs_t end = systemTime();
         acquireFence2->signalForTest(12);
 
@@ -180,7 +184,8 @@
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
 
-        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10,
+                                                             gui::GameMode::Unsupported);
 
         EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -197,7 +202,8 @@
                                                          1ULL /* bufferId */,
                                                          HAL_PIXEL_FORMAT_RGBA_8888,
                                                          0ULL /*usage*/);
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         acquireFence->signalForTest(12);
 
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -232,11 +238,13 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
 
-        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10,
+                                                             gui::GameMode::Unsupported);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
     }
@@ -246,7 +254,8 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10,
+                                                             gui::GameMode::Unsupported);
         EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferlessSurfaceFrame1 =
@@ -255,7 +264,8 @@
         FrameTimelineInfo ftInfo2;
         ftInfo2.vsyncId = 4;
         ftInfo2.inputEventId = 0;
-        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10);
+        layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10,
+                                                             gui::GameMode::Unsupported);
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferlessSurfaceFrame2 = layer->mDrawingState.bufferlessSurfaceFramesTX[4];
@@ -275,7 +285,8 @@
         FrameTimelineInfo ftInfo3;
         ftInfo3.vsyncId = 3;
         ftInfo3.inputEventId = 0;
-        layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3);
+        layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo3,
+                         gui::GameMode::Unsupported);
         EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -302,58 +313,6 @@
         EXPECT_EQ(PresentState::Presented, bufferSurfaceFrameTX->getPresentState());
     }
 
-    void PendingSurfaceFramesRemovedAfterClassification() {
-        sp<Layer> layer = createLayer();
-
-        sp<Fence> fence1(sp<Fence>::make());
-        auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
-        BufferData bufferData;
-        bufferData.acquireFence = fence1;
-        bufferData.frameNumber = 1;
-        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
-        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
-        std::shared_ptr<renderengine::ExternalTexture> externalTexture1 = std::make_shared<
-                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
-                                                         1ULL /* bufferId */,
-                                                         HAL_PIXEL_FORMAT_RGBA_8888,
-                                                         0ULL /*usage*/);
-        FrameTimelineInfo ftInfo;
-        ftInfo.vsyncId = 1;
-        ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
-        ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
-        const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
-
-        sp<Fence> fence2(sp<Fence>::make());
-        auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
-        bufferData.acquireFence = fence2;
-        bufferData.frameNumber = 1;
-        bufferData.flags |= BufferData::BufferDataChange::fenceChanged;
-        bufferData.flags |= BufferData::BufferDataChange::frameNumberChanged;
-        std::shared_ptr<renderengine::ExternalTexture> externalTexture2 = std::make_shared<
-                renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
-                                                         1ULL /* bufferId */,
-                                                         HAL_PIXEL_FORMAT_RGBA_8888,
-                                                         0ULL /*usage*/);
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
-        acquireFence2->signalForTest(12);
-
-        ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
-        auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
-
-        commitTransaction(layer.get());
-        layer->updateTexImage(15);
-
-        // Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
-        // pendingJankClassifications.
-        EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
-        presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
-                                         /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
-        layer->releasePendingBuffer(25);
-
-        EXPECT_EQ(0u, layer->mPendingJankClassifications.size());
-    }
-
     void BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer() {
         sp<Layer> layer = createLayer();
 
@@ -372,7 +331,8 @@
         FrameTimelineInfo ftInfo;
         ftInfo.vsyncId = 1;
         ftInfo.inputEventId = 0;
-        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
+        layer->setBuffer(externalTexture1, bufferData, 10, 20, false, ftInfo,
+                         gui::GameMode::Unsupported);
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
         const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -392,7 +352,8 @@
         FrameTimelineInfo ftInfoInv;
         ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
         ftInfoInv.inputEventId = 0;
-        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv);
+        layer->setBuffer(externalTexture2, bufferData, 10, 20, false, ftInfoInv,
+                         gui::GameMode::Unsupported);
         auto dropEndTime1 = systemTime();
         EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
         ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
@@ -413,7 +374,8 @@
         FrameTimelineInfo ftInfo2;
         ftInfo2.vsyncId = 2;
         ftInfo2.inputEventId = 0;
-        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2);
+        layer->setBuffer(externalTexture3, bufferData, 10, 20, false, ftInfo2,
+                         gui::GameMode::Unsupported);
         auto dropEndTime2 = systemTime();
         acquireFence3->signalForTest(12);
 
@@ -445,8 +407,7 @@
 
     void MultipleCommitsBeforeLatch() {
         sp<Layer> layer = createLayer();
-        uint32_t surfaceFramesPendingClassification = 0;
-        std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> bufferlessSurfaceFrames;
+        std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> surfaceFrames;
         for (int i = 0; i < 10; i += 2) {
             sp<Fence> fence(sp<Fence>::make());
             BufferData bufferData;
@@ -462,58 +423,52 @@
             FrameTimelineInfo ftInfo;
             ftInfo.vsyncId = 1;
             ftInfo.inputEventId = 0;
-            layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+            layer->setBuffer(externalTexture, bufferData, 10, 20, false, ftInfo,
+                             gui::GameMode::Unsupported);
             FrameTimelineInfo ftInfo2;
             ftInfo2.vsyncId = 2;
             ftInfo2.inputEventId = 0;
-            layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10);
+            layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10,
+                                                                 gui::GameMode::Unsupported);
             ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
             EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
-            auto& bufferlessSurfaceFrame =
-                    layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*vsyncId*/ 2);
-            bufferlessSurfaceFrames.push_back(bufferlessSurfaceFrame);
+
+            surfaceFrames.push_back(layer->mDrawingState.bufferSurfaceFrameTX);
+            surfaceFrames.push_back(
+                    layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*vsyncId*/ 2));
 
             commitTransaction(layer.get());
-            surfaceFramesPendingClassification += 2;
-            EXPECT_EQ(surfaceFramesPendingClassification,
-                      layer->mPendingJankClassifications.size());
         }
 
         auto presentedBufferSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
         layer->updateTexImage(15);
         // BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
         // Since we don't have access to DisplayFrame here, trigger an onPresent directly.
-        for (auto& surfaceFrame : bufferlessSurfaceFrames) {
-            surfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
-                                    /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
+        // The odd indices are the bufferless frames.
+        for (uint32_t i = 1; i < 10; i += 2) {
+            surfaceFrames[i]->onPresent(20, JankType::None, 90_Hz, 90_Hz,
+                                        /*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
         }
         presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
                                                /*displayDeadlineDelta*/ 0,
                                                /*displayPresentDelta*/ 0);
 
-        // There should be 10 bufferlessSurfaceFrames and 1 bufferSurfaceFrame
-        ASSERT_EQ(10u, surfaceFramesPendingClassification);
-        ASSERT_EQ(surfaceFramesPendingClassification, layer->mPendingJankClassifications.size());
-
         // For the frames upto 8, the bufferSurfaceFrame should have been dropped while the
         // bufferlessSurfaceFrame presented
         for (uint32_t i = 0; i < 8; i += 2) {
-            auto& bufferSurfaceFrame = layer->mPendingJankClassifications[i];
-            auto& bufferlessSurfaceFrame = layer->mPendingJankClassifications[i + 1];
+            auto bufferSurfaceFrame = surfaceFrames[i];
+            auto bufferlessSurfaceFrame = surfaceFrames[i + 1];
             EXPECT_EQ(bufferSurfaceFrame->getPresentState(), PresentState::Dropped);
             EXPECT_EQ(bufferlessSurfaceFrame->getPresentState(), PresentState::Presented);
         }
         {
-            auto& bufferSurfaceFrame = layer->mPendingJankClassifications[8u];
-            auto& bufferlessSurfaceFrame = layer->mPendingJankClassifications[9u];
+            auto bufferSurfaceFrame = surfaceFrames[8];
+            auto bufferlessSurfaceFrame = surfaceFrames[9];
             EXPECT_EQ(bufferSurfaceFrame->getPresentState(), PresentState::Presented);
             EXPECT_EQ(bufferlessSurfaceFrame->getPresentState(), PresentState::Presented);
         }
 
         layer->releasePendingBuffer(25);
-
-        // There shouldn't be any pending classifications. Everything should have been cleared.
-        EXPECT_EQ(0u, layer->mPendingJankClassifications.size());
     }
 };
 
@@ -541,10 +496,6 @@
     MultipleSurfaceFramesPresentedTogether();
 }
 
-TEST_F(TransactionSurfaceFrameTest, PendingSurfaceFramesRemovedAfterClassification) {
-    PendingSurfaceFramesRemovedAfterClassification();
-}
-
 TEST_F(TransactionSurfaceFrameTest,
        BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer) {
     BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer();
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index fb4ef70..f8f08c7 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -20,6 +20,7 @@
 #include <gui/SurfaceComposerClient.h>
 #include <cstdint>
 #include "Client.h"
+#include "Layer.h"
 
 #include <layerproto/LayerProtoHeader.h>
 #include "FrontEnd/LayerCreationArgs.h"
@@ -131,14 +132,14 @@
         // add layers and add some layer transaction
         {
             frontend::Update update;
-            update.layerCreationArgs.emplace_back(std::move(
+            update.layerCreationArgs.emplace_back(
                     getLayerCreationArgs(mParentLayerId, /*parentId=*/UNASSIGNED_LAYER_ID,
                                          /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123,
-                                         /*addToRoot=*/true)));
-            update.layerCreationArgs.emplace_back(std::move(
+                                         /*addToRoot=*/true));
+            update.layerCreationArgs.emplace_back(
                     getLayerCreationArgs(mChildLayerId, mParentLayerId,
                                          /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/456,
-                                         /*addToRoot=*/true)));
+                                         /*addToRoot=*/true));
             TransactionState transaction;
             transaction.id = 50;
             ResolvedComposerState layerState;
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
index 1cf14ae..e27af0e 100644
--- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -127,13 +127,11 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layer->setSidebandStream(stream, FrameTimelineInfo{}, 20);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(layer);
+    layer->setSidebandStream(stream, FrameTimelineInfo{}, 20, gui::GameMode::Unsupported);
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
     mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
     EXPECT_EQ(true, mTunnelModeEnabledListener->mTunnelModeEnabled);
     mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener);
-    mFlinger.mutableCurrentState().layersSortedByZ.remove(layer);
     layer = nullptr;
 
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
@@ -151,14 +149,12 @@
     sp<NativeHandle> stream =
             NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                  false);
-    layerWithSidebandStream->setSidebandStream(stream, FrameTimelineInfo{}, 20);
+    layerWithSidebandStream->setSidebandStream(stream, FrameTimelineInfo{}, 20,
+                                               gui::GameMode::Unsupported);
 
-    mFlinger.mutableCurrentState().layersSortedByZ.add(simpleLayer);
-    mFlinger.mutableCurrentState().layersSortedByZ.add(layerWithSidebandStream);
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
     EXPECT_EQ(true, mTunnelModeEnabledListener->mTunnelModeEnabled);
 
-    mFlinger.mutableCurrentState().layersSortedByZ.remove(layerWithSidebandStream);
     layerWithSidebandStream = nullptr;
     mTunnelModeEnabledReporter->updateTunnelModeStatus();
     EXPECT_EQ(false, mTunnelModeEnabledListener->mTunnelModeEnabled);
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index c22deab..b63f299 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -19,6 +19,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <scheduler/FrameTime.h>
 #include <scheduler/Timer.h>
 
 #include "Scheduler/VSyncDispatchTimerQueue.h"
@@ -50,10 +51,11 @@
     bool needsMoreSamples() const final { return false; }
     bool isVSyncInPhase(nsecs_t, Fps) final { return false; }
     void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
-    void setRenderRate(Fps) final {}
-    void onFrameBegin(TimePoint, TimePoint) final {}
+    void setRenderRate(Fps, bool) final {}
+    void onFrameBegin(TimePoint, scheduler::FrameTime) final {}
     void onFrameMissed(TimePoint) final {}
     void dump(std::string&) const final {}
+    bool isCurrentMode(const ftl::NonNull<DisplayModePtr>&) const final { return false; };
 
 protected:
     std::mutex mutable mMutex;
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 6b9ea56..918107d 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -535,6 +535,28 @@
     }
 }
 
+TEST_F(VSyncPredictorTest, isVSyncInPhaseWithRenderRate) {
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+    auto last = mNow;
+    for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+        EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+        mNow += mPeriod;
+        last = mNow;
+        tracker.addVsyncTimestamp(mNow);
+    }
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + mPeriod), Eq(mNow + 2 * mPeriod));
+
+    const auto renderRateFps = Fps::fromPeriodNsecs(mPeriod * 2);
+    tracker.setRenderRate(renderRateFps, /*applyImmediately*/ true);
+
+    EXPECT_FALSE(tracker.isVSyncInPhase(mNow, renderRateFps));
+    EXPECT_TRUE(tracker.isVSyncInPhase(mNow + mPeriod, renderRateFps));
+    EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, renderRateFps));
+    EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, renderRateFps));
+}
+
 TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) {
     auto last = mNow;
     for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
@@ -620,15 +642,15 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod), /*applyImmediately*/ false);
 
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 6 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 6 * mPeriod));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
 TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) {
@@ -640,7 +662,7 @@
         tracker.addVsyncTimestamp(mNow);
     }
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod), /*applyImmediately*/ false);
 
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
@@ -651,6 +673,211 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
 }
 
+TEST_F(VSyncPredictorTest, setRenderRateWhenRenderRateGoesDown) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+    SET_FLAG_FOR_TEST(flags::vrr_bugfix_24q4, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    Fps frameRate = Fps::fromPeriodNsecs(1000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    frameRate = Fps::fromPeriodNsecs(3000);
+    vrrTracker.setRenderRate(frameRate, /*applyImmediately*/ false);
+    EXPECT_TRUE(vrrTracker.isVSyncInPhase(2000, frameRate));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateHighIsAppliedImmediately) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // commit to a vsync in the future
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(3500), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2500), /*applyImmediately*/ false);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(8000, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(12000, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(15500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(19000, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+    EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 8000));
+    EXPECT_EQ(11500, vrrTracker.nextAnticipatedVSyncTimeFrom(10000, 10000));
+    EXPECT_EQ(13500, vrrTracker.nextAnticipatedVSyncTimeFrom(12000, 12000));
+    EXPECT_EQ(16500, vrrTracker.nextAnticipatedVSyncTimeFrom(15500, 15500));
+    EXPECT_EQ(20500, vrrTracker.nextAnticipatedVSyncTimeFrom(19000, 19000));
+
+    // matches the previous cadence
+    EXPECT_EQ(21500, vrrTracker.nextAnticipatedVSyncTimeFrom(20500, 20500));
+}
+
+TEST_F(VSyncPredictorTest, minFramePeriodDoesntApplyWhenSameWithRefreshRate) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(1000);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // Assume that the last vsync is wrong due to a vsync drift. It shouldn't matter.
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1700));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateExplicitAppliedImmediately) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000, 1000));
+
+    // commit to a vsync in the future
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 2000));
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000));
+    EXPECT_EQ(7000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(9000, vrrTracker.nextAnticipatedVSyncTimeFrom(7000, 7000));
+}
+
+TEST_F(VSyncPredictorTest, selectsClosestVsyncAfterInactivity) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(5000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4700));
+    EXPECT_EQ(10000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+
+    mClock->setNow(50000);
+    EXPECT_EQ(50500, vrrTracker.nextAnticipatedVSyncTimeFrom(50000, 10000));
+}
+
+TEST_F(VSyncPredictorTest, returnsCorrectVsyncWhenLastIsNot) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto vsyncRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), vsyncRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(Fps::fromPeriodNsecs(1000), /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(1234, 1234));
+}
+
 TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
     SET_FLAG_FOR_TEST(flags::vrr_config, true);
 
@@ -669,23 +896,80 @@
     VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
                               kMinimumSamplesForPrediction, kOutlierTolerancePercent};
 
-    vrrTracker.setRenderRate(minFrameRate);
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
     vrrTracker.addVsyncTimestamp(0);
     EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
     EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
 
-    vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(2000),
+                            {TimePoint::fromNs(1500), TimePoint::fromNs(1500)});
     EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 2000));
     EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(3500, 3500));
 
     // Miss when starting 4500 and expect the next vsync will be at 5000 (next one)
-    vrrTracker.onFrameBegin(TimePoint::fromNs(3500), TimePoint::fromNs(2500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3500),
+                            {TimePoint::fromNs(2500), TimePoint::fromNs(2500)});
     vrrTracker.onFrameMissed(TimePoint::fromNs(4500));
     EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
     EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
 
-    vrrTracker.onFrameBegin(TimePoint::fromNs(7000), TimePoint::fromNs(6500));
-    EXPECT_EQ(10500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(7000),
+                            {TimePoint::fromNs(6500), TimePoint::fromNs(6500)});
+    EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(8000, 7000));
+    EXPECT_EQ(9500, vrrTracker.nextAnticipatedVSyncTimeFrom(9000, 7000));
+}
+
+TEST_F(VSyncPredictorTest, adjustsVrrTimelineTwoClients) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+
+    // App runs ahead
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 3000));
+    EXPECT_EQ(5000, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+
+    // SF starts to catch up
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2700));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3000), {TimePoint::fromNs(0), TimePoint::fromNs(0)});
+
+    // SF misses last frame (3000) and observes that when committing (4000)
+    EXPECT_EQ(6000, vrrTracker.nextAnticipatedVSyncTimeFrom(5000, 5000));
+    EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3700));
+    vrrTracker.onFrameMissed(TimePoint::fromNs(4000));
+
+    // SF wakes up again instead of the (4000) missed frame
+    EXPECT_EQ(4500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 4000));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(4500),
+                            {TimePoint::fromNs(4500), TimePoint::fromNs(4500)});
+
+    // Timeline shifted. The app needs to get the next frame at (7500) as its last frame (6500) will
+    // be presented at (7500)
+    EXPECT_EQ(7500, vrrTracker.nextAnticipatedVSyncTimeFrom(6000, 6000));
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4500, 4500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(5500),
+                            {TimePoint::fromNs(4500), TimePoint::fromNs(4500)});
+
+    EXPECT_EQ(8500, vrrTracker.nextAnticipatedVSyncTimeFrom(7500, 7500));
+    EXPECT_EQ(6500, vrrTracker.nextAnticipatedVSyncTimeFrom(5500, 5500));
+    vrrTracker.onFrameBegin(TimePoint::fromNs(6500),
+                            {TimePoint::fromNs(5500), TimePoint::fromNs(5500)});
 }
 
 TEST_F(VSyncPredictorTest, renderRateIsPreservedForCommittedVsyncs) {
@@ -695,21 +979,22 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(2000));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ false);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
 
-    tracker.setRenderRate(Fps::fromPeriodNsecs(3000));
+    tracker.setRenderRate(Fps::fromPeriodNsecs(3000), /*applyImmediately*/ false);
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(6000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(7000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(8000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(10000));
-    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(10000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(7001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(8001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(11000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(10001), Eq(11000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(11001), Eq(14000));
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(12001), Eq(14000));
@@ -721,6 +1006,86 @@
     EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(6001), Eq(8000));
 }
 
+// b/329310308
+TEST_F(VSyncPredictorTest, renderRateChangeAfterAppliedImmediately) {
+    tracker.addVsyncTimestamp(1000);
+
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(2000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(2001), Eq(3000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(2000), /*applyImmediately*/ true);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+
+    tracker.setRenderRate(Fps::fromPeriodNsecs(4000), /*applyImmediately*/ false);
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1), Eq(1000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(1001), Eq(3000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(3001), Eq(5000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(5001), Eq(9000));
+    EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(9001), Eq(13000));
+}
+
+TEST_F(VSyncPredictorTest, timelineNotAdjustedForEarlyPresent) {
+    SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+    const int32_t kGroup = 0;
+    const auto kResolution = ui::Size(1920, 1080);
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig;
+    vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+    const ftl::NonNull<DisplayModePtr> kMode =
+            ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+                                                      kResolution, DEFAULT_DISPLAY_ID)
+                                     .setVrrConfig(std::move(vrrConfig))
+                                     .build());
+
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), kMode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+
+    constexpr auto kLastConfirmedExpectedPresentTime = TimePoint::fromNs(1000);
+    constexpr auto kLastActualSignalTime = TimePoint::fromNs(700); // presented early
+    vrrTracker.onFrameBegin(TimePoint::fromNs(1400),
+                            {kLastActualSignalTime, kLastConfirmedExpectedPresentTime});
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1400, 1000));
+    EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2000, 1000));
+}
+
+TEST_F(VSyncPredictorTest, adjustsOnlyMinFrameViolatingVrrTimeline) {
+    const auto refreshRate = Fps::fromPeriodNsecs(500);
+    auto minFrameRate = Fps::fromPeriodNsecs(1000);
+    hal::VrrConfig vrrConfig{.minFrameIntervalNs =
+                                     static_cast<int32_t>(minFrameRate.getPeriodNsecs())};
+    ftl::NonNull<DisplayModePtr> mode =
+            ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig));
+    VSyncPredictor vrrTracker{std::make_unique<ClockWrapper>(mClock), mode, kHistorySize,
+                              kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    vrrTracker.addVsyncTimestamp(0);
+
+    EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+    EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1000));
+    auto lastConfirmedSignalTime = TimePoint::fromNs(1500);
+    auto lastConfirmedExpectedPresentTime = TimePoint::fromNs(1000);
+    vrrTracker.onFrameBegin(TimePoint::fromNs(2000),
+                            {lastConfirmedSignalTime, lastConfirmedExpectedPresentTime});
+    EXPECT_EQ(3500, vrrTracker.nextAnticipatedVSyncTimeFrom(3000, 1500));
+
+    minFrameRate = Fps::fromPeriodNsecs(2000);
+    vrrTracker.setRenderRate(minFrameRate, /*applyImmediately*/ false);
+    lastConfirmedSignalTime = TimePoint::fromNs(2500);
+    lastConfirmedExpectedPresentTime = TimePoint::fromNs(2500);
+    vrrTracker.onFrameBegin(TimePoint::fromNs(3000),
+                            {lastConfirmedSignalTime, lastConfirmedExpectedPresentTime});
+    // Enough time without adjusting vsync to present with new rate on time, no need of adjustment
+    EXPECT_EQ(5500, vrrTracker.nextAnticipatedVSyncTimeFrom(4000, 3500));
+}
 } // namespace android::scheduler
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 8d9623d..51373c1 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -195,9 +195,7 @@
 
 TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
-
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
     nsecs_t const newPeriod = 5000;
 
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -207,7 +205,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, std::nullopt, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
@@ -232,7 +230,8 @@
 TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
     nsecs_t sampleTime = 0;
     nsecs_t const newPeriod = 5000;
-    mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
+    auto modePtr = displayMode(newPeriod);
+    mReactor.onDisplayModeChanged(modePtr, false);
     bool periodFlushed = true;
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
@@ -240,7 +239,9 @@
     EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 
-    mReactor.onDisplayModeChanged(displayMode(period), false);
+    modePtr = displayMode(period);
+    EXPECT_CALL(*mMockTracker, isCurrentMode(modePtr)).WillOnce(Return(true));
+    mReactor.onDisplayModeChanged(modePtr, false);
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
     EXPECT_FALSE(periodFlushed);
 }
@@ -463,8 +464,7 @@
 
 TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) {
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(2);
-    mReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(3);
 
     nsecs_t const newPeriod = 5000;
     mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
@@ -476,7 +476,7 @@
     EXPECT_FALSE(mReactor.addHwVsyncTimestamp(newPeriod, newPeriod, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
@@ -486,8 +486,7 @@
                          *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */);
 
     bool periodFlushed = true;
-    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4);
-    idleReactor.setIgnorePresentFences(true);
+    EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(5);
 
     // First, set the same period, which should only be confirmed when we receive two
     // matching callbacks
@@ -512,7 +511,7 @@
     EXPECT_FALSE(idleReactor.addHwVsyncTimestamp(20000, 5000, &periodFlushed));
     EXPECT_TRUE(periodFlushed);
 
-    EXPECT_TRUE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
+    EXPECT_FALSE(idleReactor.addPresentFence(generateSignalledFenceWithTime(0)));
 }
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 184dada..f472d8f 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -180,6 +180,12 @@
     MOCK_METHOD1(onHotplugDisconnect, void(Display));
     MOCK_METHOD(Error, setRefreshRateChangedCallbackDebugEnabled, (Display, bool));
     MOCK_METHOD(Error, notifyExpectedPresent, (Display, nsecs_t, int32_t));
+    MOCK_METHOD(
+            Error, getRequestedLuts,
+            (Display,
+             std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*));
+    MOCK_METHOD(Error, setLayerLuts,
+                (Display, Layer, std::vector<aidl::android::hardware::graphics::composer3::Lut>&));
 };
 
 } // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 602bdfc..5edd2cd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -35,6 +35,7 @@
                 (const, override));
     MOCK_METHOD(bool, isVsyncPeriodSwitchSupported, (), (const, override));
     MOCK_METHOD(void, onLayerDestroyed, (hal::HWLayerId), (override));
+    MOCK_METHOD(std::optional<ui::Size>, getPhysicalSizeInMm, (), (const override));
 
     MOCK_METHOD(hal::Error, acceptChanges, (), (override));
     MOCK_METHOD((base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>), createLayer, (),
@@ -109,6 +110,9 @@
     MOCK_METHOD(hal::Error, getOverlaySupport,
                 (aidl::android::hardware::graphics::composer3::OverlayProperties *),
                 (const override));
+    MOCK_METHOD(hal::Error, getRequestedLuts,
+                (std::vector<aidl::android::hardware::graphics::composer3::DisplayLuts::LayerLut>*),
+                (override));
 };
 
 class Layer : public HWC2::Layer {
@@ -143,6 +147,8 @@
                 (const std::string &, bool, const std::vector<uint8_t> &), (override));
     MOCK_METHOD(hal::Error, setBrightness, (float), (override));
     MOCK_METHOD(hal::Error, setBlockingRegion, (const android::Region &), (override));
+    MOCK_METHOD(hal::Error, setLuts,
+                (std::vector<aidl::android::hardware::graphics::composer3::Lut>&), (override));
 };
 
 } // namespace android::HWC2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
deleted file mode 100644
index 770bc15..0000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.cpp
+++ /dev/null
@@ -1,24 +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 "mock/DisplayHardware/MockIPowerHintSession.h"
-
-namespace android::Hwc2::mock {
-
-// Explicit default instantiation is recommended.
-MockIPowerHintSession::MockIPowerHintSession() = default;
-
-} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
deleted file mode 100644
index 27564b2..0000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ /dev/null
@@ -1,58 +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.
- */
-
-#pragma once
-
-#include "binder/Status.h"
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <aidl/android/hardware/power/IPower.h>
-#pragma clang diagnostic pop
-
-#include <gmock/gmock.h>
-
-using aidl::android::hardware::power::IPowerHintSession;
-using aidl::android::hardware::power::SessionConfig;
-using aidl::android::hardware::power::SessionHint;
-using aidl::android::hardware::power::SessionMode;
-using android::binder::Status;
-
-using namespace aidl::android::hardware::power;
-
-namespace android::Hwc2::mock {
-
-class MockIPowerHintSession : public IPowerHintSession {
-public:
-    MockIPowerHintSession();
-
-    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
-    MOCK_METHOD(bool, isRemote, (), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector<WorkDuration>&),
-                (override));
-    MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
-    MOCK_METHOD(ndk::ScopedAStatus, getSessionConfig, (SessionConfig * _aidl_return), (override));
-};
-
-} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index 8e8eb1d..4efdfe8 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -36,10 +36,12 @@
     MOCK_METHOD(void, notifyDisplayUpdateImminentAndCpuReset, (), (override));
     MOCK_METHOD(bool, usePowerHintSession, (), (override));
     MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
+    MOCK_METHOD(bool, supportsGpuReporting, (), (override));
     MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
     MOCK_METHOD(void, reportActualWorkDuration, (), (override));
     MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
     MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
+    MOCK_METHOD(void, setGpuStartTime, (DisplayId displayId, TimePoint startTime), (override));
     MOCK_METHOD(void, setGpuFenceTime,
                 (DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime), (override));
     MOCK_METHOD(void, setHwcValidateTiming,
@@ -49,8 +51,8 @@
                 (DisplayId displayId, TimePoint presentStartTime, TimePoint presentEndTime),
                 (override));
     MOCK_METHOD(void, setSkippedValidate, (DisplayId displayId, bool skipped), (override));
-    MOCK_METHOD(void, setRequiresClientComposition,
-                (DisplayId displayId, bool requiresClientComposition), (override));
+    MOCK_METHOD(void, setRequiresRenderEngine, (DisplayId displayId, bool requiresRenderEngine),
+                (override));
     MOCK_METHOD(void, setExpectedPresentTime, (TimePoint expectedPresentTime), (override));
     MOCK_METHOD(void, setSfPresentTiming, (TimePoint presentFenceTime, TimePoint presentEndTime),
                 (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
index ae41e7e..af1862d 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -44,10 +44,10 @@
     MOCK_METHOD(HalResult<void>, setBoost, (aidl::android::hardware::power::Boost, int32_t),
                 (override));
     MOCK_METHOD(HalResult<void>, setMode, (aidl::android::hardware::power::Mode, bool), (override));
-    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
                 createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
                 (override));
-    MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+    MOCK_METHOD(HalResult<std::shared_ptr<android::power::PowerHintSessionWrapper>>,
                 createHintSessionWithConfig,
                 (int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
                  int64_t durationNanos, aidl::android::hardware::power::SessionTag tag,
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
new file mode 100644
index 0000000..d383283
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mock/DisplayHardware/MockPowerHintSessionWrapper.h"
+
+namespace android::Hwc2::mock {
+
+// Explicit default instantiation is recommended.
+MockPowerHintSessionWrapper::MockPowerHintSessionWrapper()
+      : power::PowerHintSessionWrapper(nullptr) {}
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
new file mode 100644
index 0000000..bc6950c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHintSessionWrapper.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "binder/Status.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <aidl/android/hardware/power/IPower.h>
+#include <powermanager/PowerHintSessionWrapper.h>
+#pragma clang diagnostic pop
+
+#include <gmock/gmock.h>
+
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionConfig;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
+using android::binder::Status;
+
+using namespace aidl::android::hardware::power;
+
+namespace android::Hwc2::mock {
+
+class MockPowerHintSessionWrapper : public power::PowerHintSessionWrapper {
+public:
+    MockPowerHintSessionWrapper();
+
+    MOCK_METHOD(power::HalResult<void>, updateTargetWorkDuration, (int64_t), (override));
+    MOCK_METHOD(power::HalResult<void>, reportActualWorkDuration,
+                (const ::std::vector<WorkDuration>&), (override));
+    MOCK_METHOD(power::HalResult<void>, pause, (), (override));
+    MOCK_METHOD(power::HalResult<void>, resume, (), (override));
+    MOCK_METHOD(power::HalResult<void>, close, (), (override));
+    MOCK_METHOD(power::HalResult<void>, sendHint, (SessionHint), (override));
+    MOCK_METHOD(power::HalResult<void>, setThreads, (const ::std::vector<int32_t>&), (override));
+    MOCK_METHOD(power::HalResult<void>, setMode, (SessionMode, bool), (override));
+    MOCK_METHOD(power::HalResult<SessionConfig>, getSessionConfig, (), (override));
+};
+
+} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
index 7b18a82..4d35d4d 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
@@ -22,14 +22,13 @@
 
 namespace android::mock {
 
-inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode,
-                                                    bool allowGroupSwitching, float minFps,
-                                                    float maxFps) {
+inline gui::DisplayModeSpecs createDisplayModeSpecs(DisplayModeId defaultMode, Fps maxFps,
+                                                    bool allowGroupSwitching = false) {
     gui::DisplayModeSpecs specs;
     specs.defaultMode = ftl::to_underlying(defaultMode);
     specs.allowGroupSwitching = allowGroupSwitching;
-    specs.primaryRanges.physical.min = minFps;
-    specs.primaryRanges.physical.max = maxFps;
+    specs.primaryRanges.physical.min = 0.f;
+    specs.primaryRanges.physical.max = maxFps.getValue();
     specs.primaryRanges.render = specs.primaryRanges.physical;
     specs.appRequestRanges = specs.primaryRanges;
     return specs;
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index e2b0ed1..7398cbe 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -24,21 +24,11 @@
 
 class EventThread : public android::EventThread {
 public:
-    static constexpr auto kCallingUid = static_cast<uid_t>(0);
-
     EventThread();
     ~EventThread() override;
 
-    // TODO(b/302035909): workaround otherwise gtest complains about
-    //  error: no viable conversion from
-    //  'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
-    //  tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
-    sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
-        return createEventConnection(false, flags);
-    }
-    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
-                (const));
-
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (EventRegistrationFlags),
+                (const, override));
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
@@ -55,7 +45,7 @@
                 (override));
     MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
     MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
-                (const sp<android::EventThreadConnection>&), (const, override));
+                (const sp<android::EventThreadConnection>&, nsecs_t), (const, override));
     MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
     MOCK_METHOD(void, pauseVsyncCallback, (bool));
     MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 4204aa0..45f86fa 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -24,28 +24,18 @@
 class MockLayer : public Layer {
 public:
     MockLayer(SurfaceFlinger* flinger, std::string name)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {
-        EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
-                .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
-    }
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
 
     MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> uid)
-          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {
-        EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
-                .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
-    }
+          : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {}
 
     explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
 
-    MOCK_CONST_METHOD0(getType, const char*());
     MOCK_METHOD0(getFrameSelectionPriority, int32_t());
-    MOCK_CONST_METHOD0(isVisible, bool());
-    MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
+    MOCK_METHOD0(createClone, sp<Layer>());
     MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
     MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
     MOCK_CONST_METHOD0(getOwnerUid, uid_t());
-    MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace());
-    MOCK_METHOD(bool, isFrontBuffered, (), (const, override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 4ca0542..25dd68e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -26,19 +26,21 @@
     MOCK_METHOD(void, requestHardwareVsync, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
     MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
-    MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
     MOCK_METHOD(void, onChoreographerAttached, (), (override));
     MOCK_METHOD(void, onExpectedPresentTimePosted, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps),
                 (override));
+    MOCK_METHOD(void, onCommitNotComposited, (), (override));
+    MOCK_METHOD(void, vrrDisplayIdle, (bool), (override));
 };
 
 struct NoOpSchedulerCallback final : ISchedulerCallback {
     void requestHardwareVsync(PhysicalDisplayId, bool) override {}
     void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
     void kernelTimerChanged(bool) override {}
-    void triggerOnFrameRateOverridesChanged() override {}
     void onChoreographerAttached() override {}
     void onExpectedPresentTimePosted(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+    void onCommitNotComposited() override {}
+    void vrrDisplayIdle(bool) override {}
 };
 
 } // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 6d10a5c..8d6d1d3 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -18,6 +18,8 @@
 
 #include <gmock/gmock.h>
 
+#include <scheduler/FrameTime.h>
+
 #include "Scheduler/VSyncTracker.h"
 
 namespace android::mock {
@@ -36,10 +38,11 @@
     MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
     MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (override));
     MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
-    MOCK_METHOD(void, setRenderRate, (Fps), (override));
-    MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
+    MOCK_METHOD(void, setRenderRate, (Fps, bool), (override));
+    MOCK_METHOD(void, onFrameBegin, (TimePoint, scheduler::FrameTime), (override));
     MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
     MOCK_METHOD(void, dump, (std::string&), (const, override));
+    MOCK_METHOD(bool, isCurrentMode, (const ftl::NonNull<DisplayModePtr>&), (const, override));
 };
 
 } // namespace android::mock
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 38c422a..253bad7 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,10 +33,6 @@
     static const Color WHITE;
     static const Color BLACK;
     static const Color TRANSPARENT;
-
-    half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
-
-    half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
 };
 
 const Color Color::RED{255, 0, 0, 255};
@@ -78,21 +74,13 @@
     static void applyMatrix(half3& color, const mat3& mat) {
         half3 ret = half3(0);
 
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 3; j++) {
+        for (size_t i = 0; i < 3; i++) {
+            for (size_t j = 0; j < 3; j++) {
                 ret[i] = ret[i] + color[j] * mat[j][i];
             }
         }
         color = ret;
     }
-
-    static half3 toHalf3(const Color& color) {
-        return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
-    }
-
-    static half4 toHalf4(const Color& color) {
-        return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
-    }
 };
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
index 1675584..0bedcd1 100644
--- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h
+++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
@@ -15,7 +15,7 @@
  */
 #pragma once
 
-#include <gui/AidlStatusUtil.h>
+#include <gui/AidlUtil.h>
 #include <gui/SyncScreenCaptureListener.h>
 #include <private/gui/ComposerServiceAIDL.h>
 #include <ui/FenceResult.h>
@@ -39,7 +39,7 @@
         const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
-        captureArgs.dataspace = ui::Dataspace::V0_SRGB;
+        captureArgs.captureArgs.dataspace = static_cast<int32_t>(ui::Dataspace::V0_SRGB);
         const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
         binder::Status status = sf->captureDisplay(captureArgs, captureListener);
         status_t err = statusTFromBinderStatus(status);
@@ -77,7 +77,7 @@
         const auto sf = ComposerServiceAIDL::getComposerService();
         SurfaceComposerClient::Transaction().apply(true);
 
-        captureArgs.dataspace = ui::Dataspace::V0_SRGB;
+        captureArgs.captureArgs.dataspace = static_cast<int32_t>(ui::Dataspace::V0_SRGB);
         const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
         binder::Status status = sf->captureLayers(captureArgs, captureListener);
         status_t err = statusTFromBinderStatus(status);
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 2002bdf..4735ae5 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -33,19 +33,19 @@
     ],
 
     aidl: {
-       local_include_dirs: ["include"],
-       include_dirs: [
-           "hardware/interfaces/vibrator/aidl/android/hardware/vibrator",
-       ],
-       export_aidl_headers: true
+        local_include_dirs: ["include"],
+        include_dirs: [
+            "hardware/interfaces/vibrator/aidl/android/hardware/vibrator",
+        ],
+        export_aidl_headers: true,
     },
 
     shared_libs: [
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index 63a2bd0..af48673 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -1,13 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "libvibratorservice_test",
-      "options": [
-        // TODO(b/293603710): Fix flakiness
-        {
-          "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
-        }
-      ]
+      "name": "libvibratorservice_test"
     }
   ],
   "postsubmit": [
diff --git a/services/vibratorservice/VibratorHalController.cpp b/services/vibratorservice/VibratorHalController.cpp
index c1795f5..283a5f0 100644
--- a/services/vibratorservice/VibratorHalController.cpp
+++ b/services/vibratorservice/VibratorHalController.cpp
@@ -16,9 +16,9 @@
 
 #define LOG_TAG "VibratorHalController"
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/binder_manager.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/IVibrator.h>
-#include <binder/IServiceManager.h>
 #include <hardware/vibrator.h>
 
 #include <utils/Log.h>
@@ -27,10 +27,10 @@
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -38,7 +38,7 @@
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -53,10 +53,14 @@
         return nullptr;
     }
 
-    sp<Aidl::IVibrator> aidlHal = waitForVintfService<Aidl::IVibrator>();
-    if (aidlHal) {
-        ALOGV("Successfully connected to Vibrator HAL AIDL service.");
-        return std::make_shared<AidlHalWrapper>(std::move(scheduler), aidlHal);
+    auto serviceName = std::string(Aidl::IVibrator::descriptor) + "/default";
+    if (AServiceManager_isDeclared(serviceName.c_str())) {
+        std::shared_ptr<Aidl::IVibrator> hal = Aidl::IVibrator::fromBinder(
+                ndk::SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
+        if (hal) {
+            ALOGV("Successfully connected to Vibrator HAL AIDL service.");
+            return std::make_shared<AidlHalWrapper>(std::move(scheduler), std::move(hal));
+        }
     }
 
     sp<V1_0::IVibrator> halV1_0 = V1_0::IVibrator::getService();
diff --git a/services/vibratorservice/VibratorHalWrapper.cpp b/services/vibratorservice/VibratorHalWrapper.cpp
index f10ba44..4ac1618 100644
--- a/services/vibratorservice/VibratorHalWrapper.cpp
+++ b/services/vibratorservice/VibratorHalWrapper.cpp
@@ -16,8 +16,8 @@
 
 #define LOG_TAG "VibratorHalWrapper"
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/IVibrator.h>
 #include <hardware/vibrator.h>
 #include <cmath>
 
@@ -26,12 +26,15 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
+using aidl::android::hardware::vibrator::PwleV2Primitive;
+using aidl::android::hardware::vibrator::VendorEffect;
 
 using std::chrono::milliseconds;
 
@@ -39,7 +42,7 @@
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -93,9 +96,25 @@
     if (mInfoCache.mMaxAmplitudes.isFailed()) {
         mInfoCache.mMaxAmplitudes = getMaxAmplitudesInternal();
     }
+    if (mInfoCache.mMaxEnvelopeEffectSize.isFailed()) {
+        mInfoCache.mMaxEnvelopeEffectSize = getMaxEnvelopeEffectSizeInternal();
+    }
+    if (mInfoCache.mMinEnvelopeEffectControlPointDuration.isFailed()) {
+        mInfoCache.mMinEnvelopeEffectControlPointDuration =
+                getMinEnvelopeEffectControlPointDurationInternal();
+    }
+    if (mInfoCache.mMaxEnvelopeEffectControlPointDuration.isFailed()) {
+        mInfoCache.mMaxEnvelopeEffectControlPointDuration =
+                getMaxEnvelopeEffectControlPointDurationInternal();
+    }
     return mInfoCache.get();
 }
 
+HalResult<void> HalWrapper::performVendorEffect(const VendorEffect&, const std::function<void()>&) {
+    ALOGV("Skipped performVendorEffect because it's not available in Vibrator HAL");
+    return HalResult<void>::unsupported();
+}
+
 HalResult<milliseconds> HalWrapper::performComposedEffect(const std::vector<CompositeEffect>&,
                                                           const std::function<void()>&) {
     ALOGV("Skipped performComposedEffect because it's not available in Vibrator HAL");
@@ -108,6 +127,12 @@
     return HalResult<void>::unsupported();
 }
 
+HalResult<void> HalWrapper::composePwleV2(const std::vector<PwleV2Primitive>&,
+                                          const std::function<void()>&) {
+    ALOGV("Skipped composePwleV2 because it's not available in Vibrator HAL");
+    return HalResult<void>::unsupported();
+}
+
 HalResult<Capabilities> HalWrapper::getCapabilities() {
     std::lock_guard<std::mutex> lock(mInfoMutex);
     if (mInfoCache.mCapabilities.isFailed()) {
@@ -196,11 +221,28 @@
     ALOGV("Skipped getMaxAmplitudes because it's not available in Vibrator HAL");
     return HalResult<std::vector<float>>::unsupported();
 }
+HalResult<int32_t> HalWrapper::getMaxEnvelopeEffectSizeInternal() {
+    ALOGV("Skipped getMaxEnvelopeEffectSizeInternal because it's not available "
+          "in Vibrator HAL");
+    return HalResult<int32_t>::unsupported();
+}
+
+HalResult<milliseconds> HalWrapper::getMinEnvelopeEffectControlPointDurationInternal() {
+    ALOGV("Skipped getMinEnvelopeEffectControlPointDurationInternal because it's not "
+          "available in Vibrator HAL");
+    return HalResult<milliseconds>::unsupported();
+}
+
+HalResult<milliseconds> HalWrapper::getMaxEnvelopeEffectControlPointDurationInternal() {
+    ALOGV("Skipped getMaxEnvelopeEffectControlPointDurationInternal because it's not "
+          "available in Vibrator HAL");
+    return HalResult<milliseconds>::unsupported();
+}
 
 // -------------------------------------------------------------------------------------------------
 
 HalResult<void> AidlHalWrapper::ping() {
-    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(AIBinder_ping(getHal()->asBinder().get()));
 }
 
 void AidlHalWrapper::tryReconnect() {
@@ -208,7 +250,7 @@
     if (!result.isOk()) {
         return;
     }
-    sp<Aidl::IVibrator> newHandle = result.value();
+    std::shared_ptr<Aidl::IVibrator> newHandle = result.value();
     if (newHandle) {
         std::lock_guard<std::mutex> lock(mHandleMutex);
         mHandle = std::move(newHandle);
@@ -220,7 +262,8 @@
     HalResult<Capabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & Capabilities::ON_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
 
     auto ret = HalResultFactory::fromStatus(getHal()->on(timeout.count(), cb));
     if (!supportsCallback && ret.isOk()) {
@@ -255,13 +298,14 @@
     HalResult<Capabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & Capabilities::PERFORM_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
 
     int32_t lengthMs;
-    auto result = getHal()->perform(effect, strength, cb, &lengthMs);
+    auto status = getHal()->perform(effect, strength, cb, &lengthMs);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResultFactory::fromStatus<milliseconds>(result, length);
+    auto ret = HalResultFactory::fromStatus<milliseconds>(std::move(status), length);
     if (!supportsCallback && ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -269,11 +313,18 @@
     return ret;
 }
 
+HalResult<void> AidlHalWrapper::performVendorEffect(
+        const VendorEffect& effect, const std::function<void()>& completionCallback) {
+    // This method should always support callbacks, so no need to double check.
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
+    return HalResultFactory::fromStatus(getHal()->performVendorEffect(effect, cb));
+}
+
 HalResult<milliseconds> AidlHalWrapper::performComposedEffect(
         const std::vector<CompositeEffect>& primitives,
         const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
-    auto cb = new HalCallbackWrapper(completionCallback);
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
 
     auto durations = getPrimitiveDurations().valueOr({});
     milliseconds duration(0);
@@ -294,40 +345,47 @@
 HalResult<void> AidlHalWrapper::performPwleEffect(const std::vector<PrimitivePwle>& primitives,
                                                   const std::function<void()>& completionCallback) {
     // This method should always support callbacks, so no need to double check.
-    auto cb = new HalCallbackWrapper(completionCallback);
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
     return HalResultFactory::fromStatus(getHal()->composePwle(primitives, cb));
 }
 
+HalResult<void> AidlHalWrapper::composePwleV2(const std::vector<PwleV2Primitive>& composite,
+                                              const std::function<void()>& completionCallback) {
+    // This method should always support callbacks, so no need to double check.
+    auto cb = ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback);
+    return HalResultFactory::fromStatus(getHal()->composePwleV2(composite, cb));
+}
+
 HalResult<Capabilities> AidlHalWrapper::getCapabilitiesInternal() {
-    int32_t capabilities = 0;
-    auto result = getHal()->getCapabilities(&capabilities);
-    return HalResultFactory::fromStatus<Capabilities>(result,
-                                                      static_cast<Capabilities>(capabilities));
+    int32_t cap = 0;
+    auto status = getHal()->getCapabilities(&cap);
+    auto capabilities = static_cast<Capabilities>(cap);
+    return HalResultFactory::fromStatus<Capabilities>(std::move(status), capabilities);
 }
 
 HalResult<std::vector<Effect>> AidlHalWrapper::getSupportedEffectsInternal() {
     std::vector<Effect> supportedEffects;
-    auto result = getHal()->getSupportedEffects(&supportedEffects);
-    return HalResultFactory::fromStatus<std::vector<Effect>>(result, supportedEffects);
+    auto status = getHal()->getSupportedEffects(&supportedEffects);
+    return HalResultFactory::fromStatus<std::vector<Effect>>(std::move(status), supportedEffects);
 }
 
 HalResult<std::vector<Braking>> AidlHalWrapper::getSupportedBrakingInternal() {
     std::vector<Braking> supportedBraking;
-    auto result = getHal()->getSupportedBraking(&supportedBraking);
-    return HalResultFactory::fromStatus<std::vector<Braking>>(result, supportedBraking);
+    auto status = getHal()->getSupportedBraking(&supportedBraking);
+    return HalResultFactory::fromStatus<std::vector<Braking>>(std::move(status), supportedBraking);
 }
 
 HalResult<std::vector<CompositePrimitive>> AidlHalWrapper::getSupportedPrimitivesInternal() {
     std::vector<CompositePrimitive> supportedPrimitives;
-    auto result = getHal()->getSupportedPrimitives(&supportedPrimitives);
-    return HalResultFactory::fromStatus<std::vector<CompositePrimitive>>(result,
+    auto status = getHal()->getSupportedPrimitives(&supportedPrimitives);
+    return HalResultFactory::fromStatus<std::vector<CompositePrimitive>>(std::move(status),
                                                                          supportedPrimitives);
 }
 
 HalResult<std::vector<milliseconds>> AidlHalWrapper::getPrimitiveDurationsInternal(
         const std::vector<CompositePrimitive>& supportedPrimitives) {
     std::vector<milliseconds> durations;
-    constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+    constexpr auto primitiveRange = ndk::enum_range<CompositePrimitive>();
     constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
     durations.resize(primitiveCount);
 
@@ -340,8 +398,8 @@
             continue;
         }
         int32_t duration = 0;
-        auto result = getHal()->getPrimitiveDuration(primitive, &duration);
-        auto halResult = HalResultFactory::fromStatus<int32_t>(result, duration);
+        auto status = getHal()->getPrimitiveDuration(primitive, &duration);
+        auto halResult = HalResultFactory::fromStatus<int32_t>(std::move(status), duration);
         if (halResult.isUnsupported()) {
             // Should not happen, supported primitives should always support requesting duration.
             ALOGE("Supported primitive %zu returned unsupported for getPrimitiveDuration",
@@ -349,7 +407,7 @@
         }
         if (halResult.isFailed()) {
             // Fail entire request if one request has failed.
-            return HalResult<std::vector<milliseconds>>::failed(result.toString8().c_str());
+            return HalResult<std::vector<milliseconds>>::failed(halResult.errorMessage());
         }
         durations[primitiveIdx] = milliseconds(duration);
     }
@@ -359,59 +417,77 @@
 
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDelayMaxInternal() {
     int32_t delay = 0;
-    auto result = getHal()->getCompositionDelayMax(&delay);
-    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
+    auto status = getHal()->getCompositionDelayMax(&delay);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(delay));
 }
 
 HalResult<milliseconds> AidlHalWrapper::getPrimitiveDurationMaxInternal() {
     int32_t delay = 0;
-    auto result = getHal()->getPwlePrimitiveDurationMax(&delay);
-    return HalResultFactory::fromStatus<milliseconds>(result, milliseconds(delay));
+    auto status = getHal()->getPwlePrimitiveDurationMax(&delay);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(delay));
 }
 
 HalResult<int32_t> AidlHalWrapper::getCompositionSizeMaxInternal() {
     int32_t size = 0;
-    auto result = getHal()->getCompositionSizeMax(&size);
-    return HalResultFactory::fromStatus<int32_t>(result, size);
+    auto status = getHal()->getCompositionSizeMax(&size);
+    return HalResultFactory::fromStatus<int32_t>(std::move(status), size);
 }
 
 HalResult<int32_t> AidlHalWrapper::getPwleSizeMaxInternal() {
     int32_t size = 0;
-    auto result = getHal()->getPwleCompositionSizeMax(&size);
-    return HalResultFactory::fromStatus<int32_t>(result, size);
+    auto status = getHal()->getPwleCompositionSizeMax(&size);
+    return HalResultFactory::fromStatus<int32_t>(std::move(status), size);
 }
 
 HalResult<float> AidlHalWrapper::getMinFrequencyInternal() {
     float minFrequency = 0;
-    auto result = getHal()->getFrequencyMinimum(&minFrequency);
-    return HalResultFactory::fromStatus<float>(result, minFrequency);
+    auto status = getHal()->getFrequencyMinimum(&minFrequency);
+    return HalResultFactory::fromStatus<float>(std::move(status), minFrequency);
 }
 
 HalResult<float> AidlHalWrapper::getResonantFrequencyInternal() {
     float f0 = 0;
-    auto result = getHal()->getResonantFrequency(&f0);
-    return HalResultFactory::fromStatus<float>(result, f0);
+    auto status = getHal()->getResonantFrequency(&f0);
+    return HalResultFactory::fromStatus<float>(std::move(status), f0);
 }
 
 HalResult<float> AidlHalWrapper::getFrequencyResolutionInternal() {
     float frequencyResolution = 0;
-    auto result = getHal()->getFrequencyResolution(&frequencyResolution);
-    return HalResultFactory::fromStatus<float>(result, frequencyResolution);
+    auto status = getHal()->getFrequencyResolution(&frequencyResolution);
+    return HalResultFactory::fromStatus<float>(std::move(status), frequencyResolution);
 }
 
 HalResult<float> AidlHalWrapper::getQFactorInternal() {
     float qFactor = 0;
-    auto result = getHal()->getQFactor(&qFactor);
-    return HalResultFactory::fromStatus<float>(result, qFactor);
+    auto status = getHal()->getQFactor(&qFactor);
+    return HalResultFactory::fromStatus<float>(std::move(status), qFactor);
 }
 
 HalResult<std::vector<float>> AidlHalWrapper::getMaxAmplitudesInternal() {
     std::vector<float> amplitudes;
-    auto result = getHal()->getBandwidthAmplitudeMap(&amplitudes);
-    return HalResultFactory::fromStatus<std::vector<float>>(result, amplitudes);
+    auto status = getHal()->getBandwidthAmplitudeMap(&amplitudes);
+    return HalResultFactory::fromStatus<std::vector<float>>(std::move(status), amplitudes);
 }
 
-sp<Aidl::IVibrator> AidlHalWrapper::getHal() {
+HalResult<int32_t> AidlHalWrapper::getMaxEnvelopeEffectSizeInternal() {
+    int32_t size = 0;
+    auto status = getHal()->getPwleV2CompositionSizeMax(&size);
+    return HalResultFactory::fromStatus<int32_t>(std::move(status), size);
+}
+
+HalResult<milliseconds> AidlHalWrapper::getMinEnvelopeEffectControlPointDurationInternal() {
+    int32_t durationMs = 0;
+    auto status = getHal()->getPwleV2PrimitiveDurationMinMillis(&durationMs);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(durationMs));
+}
+
+HalResult<milliseconds> AidlHalWrapper::getMaxEnvelopeEffectControlPointDurationInternal() {
+    int32_t durationMs = 0;
+    auto status = getHal()->getPwleV2PrimitiveDurationMaxMillis(&durationMs);
+    return HalResultFactory::fromStatus<milliseconds>(std::move(status), milliseconds(durationMs));
+}
+
+std::shared_ptr<Aidl::IVibrator> AidlHalWrapper::getHal() {
     std::lock_guard<std::mutex> lock(mHandleMutex);
     return mHandle;
 }
@@ -420,8 +496,7 @@
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::ping() {
-    auto result = getHal()->ping();
-    return HalResultFactory::fromReturn(result);
+    return HalResultFactory::fromReturn(getHal()->ping());
 }
 
 template <typename I>
@@ -436,8 +511,8 @@
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::on(milliseconds timeout,
                                       const std::function<void()>& completionCallback) {
-    auto result = getHal()->on(timeout.count());
-    auto ret = HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->on(timeout.count());
+    auto ret = HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, timeout);
     }
@@ -446,15 +521,15 @@
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::off() {
-    auto result = getHal()->off();
-    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->off();
+    return HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
 HalResult<void> HidlHalWrapper<I>::setAmplitude(float amplitude) {
     uint8_t amp = static_cast<uint8_t>(amplitude * std::numeric_limits<uint8_t>::max());
-    auto result = getHal()->setAmplitude(amp);
-    return HalResultFactory::fromStatus(result.withDefault(V1_0::Status::UNKNOWN_ERROR));
+    auto status = getHal()->setAmplitude(amp);
+    return HalResultFactory::fromStatus(status.withDefault(V1_0::Status::UNKNOWN_ERROR));
 }
 
 template <typename I>
@@ -480,7 +555,7 @@
     hardware::Return<bool> result = getHal()->supportsAmplitudeControl();
     Capabilities capabilities =
             result.withDefault(false) ? Capabilities::AMPLITUDE_CONTROL : Capabilities::NONE;
-    return HalResultFactory::fromReturn<Capabilities>(result, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(std::move(result), capabilities);
 }
 
 template <typename I>
@@ -499,7 +574,7 @@
     auto result = std::invoke(performFn, handle, effect, effectStrength, effectCallback);
     milliseconds length = milliseconds(lengthMs);
 
-    auto ret = HalResultFactory::fromReturn<milliseconds>(result, status, length);
+    auto ret = HalResultFactory::fromReturn<milliseconds>(std::move(result), status, length);
     if (ret.isOk()) {
         mCallbackScheduler->schedule(completionCallback, length);
     }
@@ -604,7 +679,7 @@
     sp<V1_3::IVibrator> hal = getHal();
     auto amplitudeResult = hal->supportsAmplitudeControl();
     if (!amplitudeResult.isOk()) {
-        return HalResultFactory::fromReturn<Capabilities>(amplitudeResult, capabilities);
+        return HalResultFactory::fromReturn<Capabilities>(std::move(amplitudeResult), capabilities);
     }
 
     auto externalControlResult = hal->supportsExternalControl();
@@ -619,7 +694,8 @@
         }
     }
 
-    return HalResultFactory::fromReturn<Capabilities>(externalControlResult, capabilities);
+    return HalResultFactory::fromReturn<Capabilities>(std::move(externalControlResult),
+                                                      capabilities);
 }
 
 // -------------------------------------------------------------------------------------------------
diff --git a/services/vibratorservice/VibratorManagerHalController.cpp b/services/vibratorservice/VibratorManagerHalController.cpp
index aa5b7fc..ba35d15 100644
--- a/services/vibratorservice/VibratorManagerHalController.cpp
+++ b/services/vibratorservice/VibratorManagerHalController.cpp
@@ -20,7 +20,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -29,10 +29,15 @@
 std::shared_ptr<ManagerHalWrapper> connectManagerHal(std::shared_ptr<CallbackScheduler> scheduler) {
     static bool gHalExists = true;
     if (gHalExists) {
-        sp<Aidl::IVibratorManager> hal = waitForVintfService<Aidl::IVibratorManager>();
-        if (hal) {
-            ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
-            return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler), hal);
+        auto serviceName = std::string(Aidl::IVibratorManager::descriptor) + "/default";
+        if (AServiceManager_isDeclared(serviceName.c_str())) {
+            std::shared_ptr<Aidl::IVibratorManager> hal = Aidl::IVibratorManager::fromBinder(
+                    ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+            if (hal) {
+                ALOGV("Successfully connected to VibratorManager HAL AIDL service.");
+                return std::make_shared<AidlManagerHalWrapper>(std::move(scheduler),
+                                                               std::move(hal));
+            }
         }
     }
 
diff --git a/services/vibratorservice/VibratorManagerHalWrapper.cpp b/services/vibratorservice/VibratorManagerHalWrapper.cpp
index 1341266..93ec781 100644
--- a/services/vibratorservice/VibratorManagerHalWrapper.cpp
+++ b/services/vibratorservice/VibratorManagerHalWrapper.cpp
@@ -20,7 +20,7 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
-namespace Aidl = android::hardware::vibrator;
+namespace Aidl = aidl::android::hardware::vibrator;
 
 namespace android {
 
@@ -75,10 +75,11 @@
 
 std::shared_ptr<HalWrapper> AidlManagerHalWrapper::connectToVibrator(
         int32_t vibratorId, std::shared_ptr<CallbackScheduler> callbackScheduler) {
-    std::function<HalResult<sp<Aidl::IVibrator>>()> reconnectFn = [=, this]() {
-        sp<Aidl::IVibrator> vibrator;
-        auto result = this->getHal()->getVibrator(vibratorId, &vibrator);
-        return HalResultFactory::fromStatus<sp<Aidl::IVibrator>>(result, vibrator);
+    std::function<HalResult<std::shared_ptr<Aidl::IVibrator>>()> reconnectFn = [=, this]() {
+        std::shared_ptr<Aidl::IVibrator> vibrator;
+        auto status = this->getHal()->getVibrator(vibratorId, &vibrator);
+        return HalResultFactory::fromStatus<std::shared_ptr<Aidl::IVibrator>>(std::move(status),
+                                                                              vibrator);
     };
     auto result = reconnectFn();
     if (!result.isOk()) {
@@ -93,11 +94,13 @@
 }
 
 HalResult<void> AidlManagerHalWrapper::ping() {
-    return HalResultFactory::fromStatus(IInterface::asBinder(getHal())->pingBinder());
+    return HalResultFactory::fromStatus(AIBinder_ping(getHal()->asBinder().get()));
 }
 
 void AidlManagerHalWrapper::tryReconnect() {
-    sp<Aidl::IVibratorManager> newHandle = checkVintfService<Aidl::IVibratorManager>();
+    auto aidlServiceName = std::string(Aidl::IVibratorManager::descriptor) + "/default";
+    std::shared_ptr<Aidl::IVibratorManager> newHandle = Aidl::IVibratorManager::fromBinder(
+            ndk::SpAIBinder(AServiceManager_checkService(aidlServiceName.c_str())));
     if (newHandle) {
         std::lock_guard<std::mutex> lock(mHandleMutex);
         mHandle = std::move(newHandle);
@@ -111,9 +114,9 @@
         return HalResult<ManagerCapabilities>::ok(*mCapabilities);
     }
     int32_t cap = 0;
-    auto result = getHal()->getCapabilities(&cap);
+    auto status = getHal()->getCapabilities(&cap);
     auto capabilities = static_cast<ManagerCapabilities>(cap);
-    auto ret = HalResultFactory::fromStatus<ManagerCapabilities>(result, capabilities);
+    auto ret = HalResultFactory::fromStatus<ManagerCapabilities>(std::move(status), capabilities);
     if (ret.isOk()) {
         // Cache copy of returned value.
         mCapabilities.emplace(ret.value());
@@ -128,8 +131,8 @@
         return HalResult<std::vector<int32_t>>::ok(*mVibratorIds);
     }
     std::vector<int32_t> ids;
-    auto result = getHal()->getVibratorIds(&ids);
-    auto ret = HalResultFactory::fromStatus<std::vector<int32_t>>(result, ids);
+    auto status = getHal()->getVibratorIds(&ids);
+    auto ret = HalResultFactory::fromStatus<std::vector<int32_t>>(std::move(status), ids);
     if (ret.isOk()) {
         // Cache copy of returned value and the individual controllers.
         mVibratorIds.emplace(ret.value());
@@ -178,7 +181,8 @@
     HalResult<ManagerCapabilities> capabilities = getCapabilities();
     bool supportsCallback = capabilities.isOk() &&
             static_cast<int32_t>(capabilities.value() & ManagerCapabilities::TRIGGER_CALLBACK);
-    auto cb = supportsCallback ? new HalCallbackWrapper(completionCallback) : nullptr;
+    auto cb = supportsCallback ? ndk::SharedRefBase::make<HalCallbackWrapper>(completionCallback)
+                               : nullptr;
     return HalResultFactory::fromStatus(getHal()->triggerSynced(cb));
 }
 
@@ -196,7 +200,7 @@
     return ret;
 }
 
-sp<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
+std::shared_ptr<Aidl::IVibratorManager> AidlManagerHalWrapper::getHal() {
     std::lock_guard<std::mutex> lock(mHandleMutex);
     return mHandle;
 }
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index 5437995..915d6c7 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -28,12 +28,12 @@
         "VibratorHalControllerBenchmarks.cpp",
     ],
     shared_libs: [
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libutils",
         "libvibratorservice",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index e11a809..5c7c9f4 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -16,28 +16,114 @@
 
 #define LOG_TAG "VibratorHalControllerBenchmarks"
 
+#include <android/binder_process.h>
 #include <benchmark/benchmark.h>
 #include <vibratorservice/VibratorHalController.h>
+#include <future>
 
-using ::android::enum_range;
-using ::android::hardware::vibrator::CompositeEffect;
-using ::android::hardware::vibrator::CompositePrimitive;
-using ::android::hardware::vibrator::Effect;
-using ::android::hardware::vibrator::EffectStrength;
+using ::aidl::android::hardware::vibrator::CompositeEffect;
+using ::aidl::android::hardware::vibrator::CompositePrimitive;
+using ::aidl::android::hardware::vibrator::Effect;
+using ::aidl::android::hardware::vibrator::EffectStrength;
 using ::benchmark::Counter;
 using ::benchmark::Fixture;
 using ::benchmark::kMicrosecond;
 using ::benchmark::State;
 using ::benchmark::internal::Benchmark;
 
+using std::chrono::milliseconds;
+
 using namespace android;
 using namespace std::chrono_literals;
 
+// Fixed number of iterations for benchmarks that trigger a vibration on the loop.
+// They require slow cleanup to ensure a stable state on each run and less noisy metrics.
+static constexpr auto VIBRATION_ITERATIONS = 500;
+
+// Timeout to wait for vibration callback completion.
+static constexpr auto VIBRATION_CALLBACK_TIMEOUT = 100ms;
+
+// Max duration the vibrator can be turned on, in milliseconds.
+static constexpr auto MAX_ON_DURATION_MS = milliseconds(UINT16_MAX);
+
+// Helper to wait for the vibrator to become idle between vibrate bench iterations.
+class HalCallback {
+public:
+    HalCallback(std::function<void()>&& waitFn, std::function<void()>&& completeFn)
+          : mWaitFn(std::move(waitFn)), mCompleteFn(std::move(completeFn)) {}
+    ~HalCallback() = default;
+
+    std::function<void()> completeFn() const { return mCompleteFn; }
+
+    void waitForComplete() const { mWaitFn(); }
+
+private:
+    std::function<void()> mWaitFn;
+    std::function<void()> mCompleteFn;
+};
+
+// Helper for vibration callbacks, kept by the Fixture until all pending callbacks are done.
+class HalCallbacks {
+public:
+    HalCallback next() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto id = mCurrentId++;
+        mPendingPromises[id] = std::promise<void>();
+        mPendingFutures[id] = mPendingPromises[id].get_future(); // Can only be called once.
+        return HalCallback([&, id]() { waitForComplete(id); }, [&, id]() { onComplete(id); });
+    }
+
+    void onComplete(int32_t id) {
+        std::unique_lock<std::mutex> lock(mMutex);
+        auto promise = mPendingPromises.find(id);
+        if (promise != mPendingPromises.end()) {
+            promise->second.set_value();
+            mPendingPromises.erase(promise);
+        }
+    }
+
+    void waitForComplete(int32_t id) {
+        // Wait until the HAL has finished processing previous vibration before starting a new one,
+        // so the HAL state is consistent on each run and metrics are less noisy. Some of the newest
+        // HAL implementations are waiting on previous vibration cleanup and might be significantly
+        // slower, so make sure we measure vibrations on a clean slate.
+        if (mPendingFutures[id].wait_for(VIBRATION_CALLBACK_TIMEOUT) == std::future_status::ready) {
+            mPendingFutures.erase(id);
+        }
+    }
+
+    void waitForPending() {
+        // Wait for pending callbacks from the test, possibly skipped with error.
+        for (auto& [id, future] : mPendingFutures) {
+            future.wait_for(VIBRATION_CALLBACK_TIMEOUT);
+        }
+        mPendingFutures.clear();
+        {
+            std::unique_lock<std::mutex> lock(mMutex);
+            mPendingPromises.clear();
+        }
+    }
+
+private:
+    std::mutex mMutex;
+    std::map<int32_t, std::promise<void>> mPendingPromises GUARDED_BY(mMutex);
+    std::map<int32_t, std::future<void>> mPendingFutures;
+    int32_t mCurrentId;
+};
+
 class VibratorBench : public Fixture {
 public:
-    void SetUp(State& /*state*/) override { mController.init(); }
+    void SetUp(State& /*state*/) override {
+        ABinderProcess_setThreadPoolMaxThreadCount(1);
+        ABinderProcess_startThreadPool();
+        mController.init();
+    }
 
-    void TearDown(State& state) override { turnVibratorOff(state); }
+    void TearDown(State& /*state*/) override {
+        turnVibratorOff();
+        disableExternalControl();
+        mCallbacks.waitForPending();
+    }
 
     static void DefaultConfig(Benchmark* b) { b->Unit(kMicrosecond); }
 
@@ -47,38 +133,59 @@
 
 protected:
     vibrator::HalController mController;
+    HalCallbacks mCallbacks;
+
+    static void SlowBenchConfig(Benchmark* b) { b->Iterations(VIBRATION_ITERATIONS); }
 
     auto getOtherArg(const State& state, std::size_t index) const { return state.range(index + 0); }
 
-    bool hasCapabilities(vibrator::Capabilities&& query, State& state) {
+    vibrator::HalResult<void> turnVibratorOff() {
+        return mController.doWithRetry<void>([](auto hal) { return hal->off(); }, "off");
+    }
+
+    vibrator::HalResult<void> disableExternalControl() {
+        auto disableExternalControlFn = [](auto hal) { return hal->setExternalControl(false); };
+        return mController.doWithRetry<void>(disableExternalControlFn, "setExternalControl false");
+    }
+
+    bool shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities query, State& state) {
         auto result = mController.getInfo().capabilities;
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
         if (!result.isOk()) {
-            return false;
+            state.SkipWithMessage("capability result is unsupported");
+            return true;
         }
-        return (result.value() & query) == query;
-    }
-
-    void turnVibratorOff(State& state) {
-        checkHalResult(halCall<void>(mController, [](auto hal) { return hal->off(); }), state);
+        if ((result.value() & query) != query) {
+            state.SkipWithMessage("missing capability");
+            return true;
+        }
+        return false;
     }
 
     template <class R>
-    bool checkHalResult(const vibrator::HalResult<R>& result, State& state) {
+    bool shouldSkipWithError(const vibrator::HalFunction<vibrator::HalResult<R>>& halFn,
+                             const char* label, State& state) {
+        return shouldSkipWithError(mController.doWithRetry<R>(halFn, label), state);
+    }
+
+    template <class R>
+    bool shouldSkipWithError(const vibrator::HalResult<R>& result, State& state) {
         if (result.isFailed()) {
             state.SkipWithError(result.errorMessage());
-            return false;
+            return true;
         }
-        return true;
+        return false;
     }
+};
 
-    template <class R>
-    vibrator::HalResult<R> halCall(vibrator::HalController& controller,
-                                   const vibrator::HalFunction<vibrator::HalResult<R>>& halFn) {
-        return controller.doWithRetry<R>(halFn, "benchmark");
+class SlowVibratorBench : public VibratorBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
     }
 };
 
@@ -91,25 +198,32 @@
 
 BENCHMARK_WRAPPER(VibratorBench, init, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         state.ResumeTiming();
+
+        // Test
         controller.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, initCached, {
+    // First call to cache values.
+    mController.init();
+
     for (auto _ : state) {
         mController.init();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, ping, {
+    auto pingFn = [](auto hal) { return hal->ping(); };
+
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [](auto hal) { return hal->ping(); });
-        state.PauseTiming();
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(pingFn, "ping", state)) {
+            return;
+        }
     }
 });
 
@@ -119,164 +233,131 @@
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, on, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, on, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, off, {
-    auto duration = 60s;
-    auto callback = []() {};
+BENCHMARK_WRAPPER(SlowVibratorBench, off, {
+    auto duration = MAX_ON_DURATION_MS;
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(ret, state)) {
-            continue;
+        auto cb = mCallbacks.next();
+        auto onFn = [&](auto hal) { return hal->on(duration, cb.completeFn()); };
+        if (shouldSkipWithError<void>(onFn, "on", state)) {
+            return;
         }
+        auto offFn = [&](auto hal) { return hal->off(); };
         state.ResumeTiming();
-        turnVibratorOff(state);
+
+        // Test
+        if (shouldSkipWithError<void>(offFn, "off", state)) {
+            return;
+        }
+
+        // Cleanup
+        state.PauseTiming();
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
+    auto duration = MAX_ON_DURATION_MS;
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
 
-    for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        auto result =
-                halCall<void>(controller, [&](auto hal) { return hal->on(duration, callback); });
-        if (!checkHalResult(result, state)) {
-            continue;
-        }
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    auto onFn = [&](auto hal) { return hal->on(duration, [&]() {}); };
+    if (shouldSkipWithError<void>(onFn, "on", state)) {
         return;
     }
 
-    auto duration = 60s;
-    auto callback = []() {};
-    auto amplitude = 1.0f;
-
-    auto onResult =
-            halCall<void>(mController, [&](auto hal) { return hal->on(duration, callback); });
-    checkHalResult(onResult, state);
-
     for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setAmplitude", state)) {
+            return;
+        }
     }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, setExternalControl, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
         return;
     }
 
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
+
     for (auto _ : state) {
-        state.PauseTiming();
-        vibrator::HalController controller;
-        controller.init();
-        state.ResumeTiming();
-        auto ret =
-                halCall<void>(controller, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto result = halCall<void>(controller,
-                                        [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(result, state);
+        // Test
+        if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(disableExternalControl(), state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
-BENCHMARK_WRAPPER(VibratorBench, setExternalControlCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
-        return;
-    }
-
-    for (auto _ : state) {
-        state.ResumeTiming();
-        auto result =
-                halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-        state.PauseTiming();
-        if (checkHalResult(result, state)) {
-            auto ret = halCall<void>(mController,
-                                     [](auto hal) { return hal->setExternalControl(false); });
-            checkHalResult(ret, state);
-        }
-    }
-});
-
-BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitudeCached, {
-    if (!hasCapabilities(vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+BENCHMARK_WRAPPER(VibratorBench, setExternalAmplitude, {
+    auto externalAmplitudeControl = vibrator::Capabilities::EXTERNAL_CONTROL &
+            vibrator::Capabilities::EXTERNAL_AMPLITUDE_CONTROL;
+    if (shouldSkipWithMissingCapabilityMessage(externalAmplitudeControl, state)) {
         return;
     }
 
     auto amplitude = 1.0f;
+    auto setAmplitudeFn = [&](auto hal) { return hal->setAmplitude(amplitude); };
+    auto enableExternalControlFn = [](auto hal) { return hal->setExternalControl(true); };
 
-    auto onResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(true); });
-    checkHalResult(onResult, state);
-
-    for (auto _ : state) {
-        auto ret =
-                halCall<void>(mController, [&](auto hal) { return hal->setAmplitude(amplitude); });
-        checkHalResult(ret, state);
+    if (shouldSkipWithError<void>(enableExternalControlFn, "setExternalControl true", state)) {
+        return;
     }
 
-    auto offResult =
-            halCall<void>(mController, [](auto hal) { return hal->setExternalControl(false); });
-    checkHalResult(offResult, state);
+    for (auto _ : state) {
+        if (shouldSkipWithError<void>(setAmplitudeFn, "setExternalAmplitude", state)) {
+            return;
+        }
+    }
 });
 
 BENCHMARK_WRAPPER(VibratorBench, getInfo, {
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
         vibrator::HalController controller;
         controller.init();
         state.ResumeTiming();
-        auto result = controller.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+
+        controller.getInfo();
     }
 });
 
@@ -285,13 +366,7 @@
     mController.getInfo();
 
     for (auto _ : state) {
-        auto result = mController.getInfo();
-        checkHalResult(result.capabilities, state);
-        checkHalResult(result.supportedEffects, state);
-        checkHalResult(result.supportedPrimitives, state);
-        checkHalResult(result.primitiveDurations, state);
-        checkHalResult(result.resonantFrequency, state);
-        checkHalResult(result.qFactor, state);
+        mController.getInfo();
     }
 });
 
@@ -312,11 +387,11 @@
             return;
         }
 
-        for (const auto& effect : enum_range<Effect>()) {
+        for (const auto& effect : ndk::enum_range<Effect>()) {
             if (std::find(supported.begin(), supported.end(), effect) == supported.end()) {
                 continue;
             }
-            for (const auto& strength : enum_range<EffectStrength>()) {
+            for (const auto& strength : ndk::enum_range<EffectStrength>()) {
                 b->Args({static_cast<long>(effect), static_cast<long>(strength)});
             }
         }
@@ -334,9 +409,16 @@
     }
 };
 
+class SlowVibratorEffectsBench : public VibratorEffectsBench {
+public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+};
+
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnEnable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -347,24 +429,26 @@
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            auto disableResult =
-                    halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-            checkHalResult(disableResult, state);
+        // Test
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
+        state.ResumeTiming();
     }
 });
 
 BENCHMARK_WRAPPER(VibratorEffectsBench, alwaysOnDisable, {
-    if (!hasCapabilities(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
-        state.SkipWithMessage("missing capability");
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::ALWAYS_ON_CONTROL, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -375,23 +459,25 @@
     int32_t id = 1;
     auto effect = getEffect(state);
     auto strength = getStrength(state);
+    auto enableFn = [&](auto hal) { return hal->alwaysOnEnable(id, effect, strength); };
+    auto disableFn = [&](auto hal) { return hal->alwaysOnDisable(id); };
 
     for (auto _ : state) {
+        // Setup
         state.PauseTiming();
-        auto enableResult = halCall<void>(mController, [&](auto hal) {
-            return hal->alwaysOnEnable(id, effect, strength);
-        });
-        if (!checkHalResult(enableResult, state)) {
-            continue;
+        if (shouldSkipWithError<void>(enableFn, "alwaysOnEnable", state)) {
+            return;
         }
         state.ResumeTiming();
-        auto disableResult =
-                halCall<void>(mController, [&](auto hal) { return hal->alwaysOnDisable(id); });
-        checkHalResult(disableResult, state);
+
+        // Test
+        if (shouldSkipWithError<void>(disableFn, "alwaysOnDisable", state)) {
+            return;
+        }
     }
 });
 
-BENCHMARK_WRAPPER(VibratorEffectsBench, performEffect, {
+BENCHMARK_WRAPPER(SlowVibratorEffectsBench, performEffect, {
     if (!hasArgs(state)) {
         state.SkipWithMessage("missing args");
         return;
@@ -399,22 +485,38 @@
 
     auto effect = getEffect(state);
     auto strength = getStrength(state);
-    auto callback = []() {};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performEffect(effect, strength, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performEffect(effect, strength, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
-class VibratorPrimitivesBench : public VibratorBench {
+class SlowVibratorPrimitivesBench : public VibratorBench {
 public:
+    static void DefaultConfig(Benchmark* b) {
+        VibratorBench::DefaultConfig(b);
+        SlowBenchConfig(b);
+    }
+
     static void DefaultArgs(Benchmark* b) {
         vibrator::HalController controller;
         auto primitivesResult = controller.getInfo().supportedPrimitives;
@@ -430,7 +532,7 @@
             return;
         }
 
-        for (const auto& primitive : enum_range<CompositePrimitive>()) {
+        for (const auto& primitive : ndk::enum_range<CompositePrimitive>()) {
             if (std::find(supported.begin(), supported.end(), primitive) == supported.end()) {
                 continue;
             }
@@ -449,9 +551,8 @@
     }
 };
 
-BENCHMARK_WRAPPER(VibratorPrimitivesBench, performComposedEffect, {
-    if (!hasCapabilities(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
-        state.SkipWithMessage("missing capability");
+BENCHMARK_WRAPPER(SlowVibratorPrimitivesBench, performComposedEffect, {
+    if (shouldSkipWithMissingCapabilityMessage(vibrator::Capabilities::COMPOSE_EFFECTS, state)) {
         return;
     }
     if (!hasArgs(state)) {
@@ -464,19 +565,29 @@
     effect.scale = 1.0f;
     effect.delayMs = static_cast<int32_t>(0);
 
-    std::vector<CompositeEffect> effects;
-    effects.push_back(effect);
-    auto callback = []() {};
+    std::vector<CompositeEffect> effects = {effect};
 
     for (auto _ : state) {
-        state.ResumeTiming();
-        auto ret = halCall<std::chrono::milliseconds>(mController, [&](auto hal) {
-            return hal->performComposedEffect(effects, callback);
-        });
+        // Setup
         state.PauseTiming();
-        if (checkHalResult(ret, state)) {
-            turnVibratorOff(state);
+        auto cb = mCallbacks.next();
+        auto performFn = [&](auto hal) {
+            return hal->performComposedEffect(effects, cb.completeFn());
+        };
+        state.ResumeTiming();
+
+        // Test
+        if (shouldSkipWithError<milliseconds>(performFn, "performComposedEffect", state)) {
+            return;
         }
+
+        // Cleanup
+        state.PauseTiming();
+        if (shouldSkipWithError(turnVibratorOff(), state)) {
+            return;
+        }
+        cb.waitForComplete();
+        state.ResumeTiming();
     }
 });
 
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalController.h b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
index f97442d..a1cb3fa 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalController.h
@@ -17,8 +17,8 @@
 #ifndef ANDROID_OS_VIBRATORHALCONTROLLER_H
 #define ANDROID_OS_VIBRATORHALCONTROLLER_H
 
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <android-base/thread_annotations.h>
-#include <android/hardware/vibrator/IVibrator.h>
 
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
diff --git a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
index 39c4eb4..4938b15 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorHalWrapper.h
@@ -17,10 +17,12 @@
 #ifndef ANDROID_OS_VIBRATORHALWRAPPER_H
 #define ANDROID_OS_VIBRATORHALWRAPPER_H
 
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
 #include <android-base/thread_annotations.h>
+#include <android/binder_manager.h>
 #include <android/hardware/vibrator/1.3/IVibrator.h>
-#include <android/hardware/vibrator/BnVibratorCallback.h>
-#include <android/hardware/vibrator/IVibrator.h>
 #include <binder/IServiceManager.h>
 
 #include <vibratorservice/VibratorCallbackScheduler.h>
@@ -98,43 +100,49 @@
 class HalResultFactory {
 public:
     template <typename T>
-    static HalResult<T> fromStatus(binder::Status status, T data) {
-        return status.isOk() ? HalResult<T>::ok(data) : fromFailedStatus<T>(status);
+    static HalResult<T> fromStatus(ndk::ScopedAStatus&& status, T data) {
+        return status.isOk() ? HalResult<T>::ok(std::move(data))
+                             : fromFailedStatus<T>(std::move(status));
     }
 
     template <typename T>
-    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status status, T data) {
-        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<T>::ok(data)
-                                                                : fromFailedStatus<T>(status);
+    static HalResult<T> fromStatus(hardware::vibrator::V1_0::Status&& status, T data) {
+        return (status == hardware::vibrator::V1_0::Status::OK)
+                ? HalResult<T>::ok(std::move(data))
+                : fromFailedStatus<T>(std::move(status));
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
-        return ret.isOk() ? HalResult<T>::ok(data) : fromFailedReturn<T, R>(ret);
+    static HalResult<T> fromReturn(hardware::Return<R>&& ret, T data) {
+        return ret.isOk() ? HalResult<T>::ok(std::move(data))
+                          : fromFailedReturn<T, R>(std::move(ret));
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromReturn(hardware::Return<R>& ret,
+    static HalResult<T> fromReturn(hardware::Return<R>&& ret,
                                    hardware::vibrator::V1_0::Status status, T data) {
-        return ret.isOk() ? fromStatus<T>(status, data) : fromFailedReturn<T, R>(ret);
+        return ret.isOk() ? fromStatus<T>(std::move(status), std::move(data))
+                          : fromFailedReturn<T, R>(std::move(ret));
     }
 
     static HalResult<void> fromStatus(status_t status) {
-        return (status == android::OK) ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+        return (status == android::OK) ? HalResult<void>::ok()
+                                       : fromFailedStatus<void>(std::move(status));
     }
 
-    static HalResult<void> fromStatus(binder::Status status) {
-        return status.isOk() ? HalResult<void>::ok() : fromFailedStatus<void>(status);
+    static HalResult<void> fromStatus(ndk::ScopedAStatus&& status) {
+        return status.isOk() ? HalResult<void>::ok() : fromFailedStatus<void>(std::move(status));
     }
 
-    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status status) {
-        return (status == hardware::vibrator::V1_0::Status::OK) ? HalResult<void>::ok()
-                                                                : fromFailedStatus<void>(status);
+    static HalResult<void> fromStatus(hardware::vibrator::V1_0::Status&& status) {
+        return (status == hardware::vibrator::V1_0::Status::OK)
+                ? HalResult<void>::ok()
+                : fromFailedStatus<void>(std::move(status));
     }
 
     template <typename R>
-    static HalResult<void> fromReturn(hardware::Return<R>& ret) {
-        return ret.isOk() ? HalResult<void>::ok() : fromFailedReturn<void, R>(ret);
+    static HalResult<void> fromReturn(hardware::Return<R>&& ret) {
+        return ret.isOk() ? HalResult<void>::ok() : fromFailedReturn<void, R>(std::move(ret));
     }
 
 private:
@@ -146,21 +154,21 @@
     }
 
     template <typename T>
-    static HalResult<T> fromFailedStatus(binder::Status status) {
-        if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION ||
-            status.transactionError() == android::UNKNOWN_TRANSACTION) {
-            // UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this is
-            // the same as the operation being unsupported by this HAL. Should not retry.
+    static HalResult<T> fromFailedStatus(ndk::ScopedAStatus&& status) {
+        if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION ||
+            status.getStatus() == STATUS_UNKNOWN_TRANSACTION) {
+            // STATUS_UNKNOWN_TRANSACTION means the HAL implementation is an older version, so this
+            // is the same as the operation being unsupported by this HAL. Should not retry.
             return HalResult<T>::unsupported();
         }
-        if (status.exceptionCode() == binder::Status::EX_TRANSACTION_FAILED) {
-            return HalResult<T>::transactionFailed(status.toString8().c_str());
+        if (status.getExceptionCode() == EX_TRANSACTION_FAILED) {
+            return HalResult<T>::transactionFailed(status.getMessage());
         }
-        return HalResult<T>::failed(status.toString8().c_str());
+        return HalResult<T>::failed(status.getMessage());
     }
 
     template <typename T>
-    static HalResult<T> fromFailedStatus(hardware::vibrator::V1_0::Status status) {
+    static HalResult<T> fromFailedStatus(hardware::vibrator::V1_0::Status&& status) {
         switch (status) {
             case hardware::vibrator::V1_0::Status::UNSUPPORTED_OPERATION:
                 return HalResult<T>::unsupported();
@@ -171,7 +179,7 @@
     }
 
     template <typename T, typename R>
-    static HalResult<T> fromFailedReturn(hardware::Return<R>& ret) {
+    static HalResult<T> fromFailedReturn(hardware::Return<R>&& ret) {
         return ret.isDeadObject() ? HalResult<T>::transactionFailed(ret.description().c_str())
                                   : HalResult<T>::failed(ret.description().c_str());
     }
@@ -179,14 +187,14 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class HalCallbackWrapper : public hardware::vibrator::BnVibratorCallback {
+class HalCallbackWrapper : public aidl::android::hardware::vibrator::BnVibratorCallback {
 public:
     HalCallbackWrapper(std::function<void()> completionCallback)
           : mCompletionCallback(completionCallback) {}
 
-    binder::Status onComplete() override {
+    ndk::ScopedAStatus onComplete() override {
         mCompletionCallback();
-        return binder::Status::ok();
+        return ndk::ScopedAStatus::ok();
     }
 
 private:
@@ -198,14 +206,15 @@
 // Vibrator HAL capabilities.
 enum class Capabilities : int32_t {
     NONE = 0,
-    ON_CALLBACK = hardware::vibrator::IVibrator::CAP_ON_CALLBACK,
-    PERFORM_CALLBACK = hardware::vibrator::IVibrator::CAP_PERFORM_CALLBACK,
-    AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_AMPLITUDE_CONTROL,
-    EXTERNAL_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_CONTROL,
-    EXTERNAL_AMPLITUDE_CONTROL = hardware::vibrator::IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL,
-    COMPOSE_EFFECTS = hardware::vibrator::IVibrator::CAP_COMPOSE_EFFECTS,
-    COMPOSE_PWLE_EFFECTS = hardware::vibrator::IVibrator::CAP_COMPOSE_PWLE_EFFECTS,
-    ALWAYS_ON_CONTROL = hardware::vibrator::IVibrator::CAP_ALWAYS_ON_CONTROL,
+    ON_CALLBACK = aidl::android::hardware::vibrator::IVibrator::CAP_ON_CALLBACK,
+    PERFORM_CALLBACK = aidl::android::hardware::vibrator::IVibrator::CAP_PERFORM_CALLBACK,
+    AMPLITUDE_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_AMPLITUDE_CONTROL,
+    EXTERNAL_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_EXTERNAL_CONTROL,
+    EXTERNAL_AMPLITUDE_CONTROL =
+            aidl::android::hardware::vibrator::IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL,
+    COMPOSE_EFFECTS = aidl::android::hardware::vibrator::IVibrator::CAP_COMPOSE_EFFECTS,
+    COMPOSE_PWLE_EFFECTS = aidl::android::hardware::vibrator::IVibrator::CAP_COMPOSE_PWLE_EFFECTS,
+    ALWAYS_ON_CONTROL = aidl::android::hardware::vibrator::IVibrator::CAP_ALWAYS_ON_CONTROL,
 };
 
 inline Capabilities operator|(Capabilities lhs, Capabilities rhs) {
@@ -230,10 +239,15 @@
 
 class Info {
 public:
+    using Effect = aidl::android::hardware::vibrator::Effect;
+    using EffectStrength = aidl::android::hardware::vibrator::EffectStrength;
+    using CompositePrimitive = aidl::android::hardware::vibrator::CompositePrimitive;
+    using Braking = aidl::android::hardware::vibrator::Braking;
+
     const HalResult<Capabilities> capabilities;
-    const HalResult<std::vector<hardware::vibrator::Effect>> supportedEffects;
-    const HalResult<std::vector<hardware::vibrator::Braking>> supportedBraking;
-    const HalResult<std::vector<hardware::vibrator::CompositePrimitive>> supportedPrimitives;
+    const HalResult<std::vector<Effect>> supportedEffects;
+    const HalResult<std::vector<Braking>> supportedBraking;
+    const HalResult<std::vector<CompositePrimitive>> supportedPrimitives;
     const HalResult<std::vector<std::chrono::milliseconds>> primitiveDurations;
     const HalResult<std::chrono::milliseconds> primitiveDelayMax;
     const HalResult<std::chrono::milliseconds> pwlePrimitiveDurationMax;
@@ -244,15 +258,15 @@
     const HalResult<float> frequencyResolution;
     const HalResult<float> qFactor;
     const HalResult<std::vector<float>> maxAmplitudes;
+    const HalResult<int32_t> maxEnvelopeEffectSize;
+    const HalResult<std::chrono::milliseconds> minEnvelopeEffectControlPointDuration;
+    const HalResult<std::chrono::milliseconds> maxEnvelopeEffectControlPointDuration;
 
     void logFailures() const {
         logFailure<Capabilities>(capabilities, "getCapabilities");
-        logFailure<std::vector<hardware::vibrator::Effect>>(supportedEffects,
-                                                            "getSupportedEffects");
-        logFailure<std::vector<hardware::vibrator::Braking>>(supportedBraking,
-                                                             "getSupportedBraking");
-        logFailure<std::vector<hardware::vibrator::CompositePrimitive>>(supportedPrimitives,
-                                                                        "getSupportedPrimitives");
+        logFailure<std::vector<Effect>>(supportedEffects, "getSupportedEffects");
+        logFailure<std::vector<Braking>>(supportedBraking, "getSupportedBraking");
+        logFailure<std::vector<CompositePrimitive>>(supportedPrimitives, "getSupportedPrimitives");
         logFailure<std::vector<std::chrono::milliseconds>>(primitiveDurations,
                                                            "getPrimitiveDuration");
         logFailure<std::chrono::milliseconds>(primitiveDelayMax, "getPrimitiveDelayMax");
@@ -265,6 +279,11 @@
         logFailure<float>(frequencyResolution, "getFrequencyResolution");
         logFailure<float>(qFactor, "getQFactor");
         logFailure<std::vector<float>>(maxAmplitudes, "getMaxAmplitudes");
+        logFailure<int32_t>(maxEnvelopeEffectSize, "getMaxEnvelopeEffectSize");
+        logFailure<std::chrono::milliseconds>(minEnvelopeEffectControlPointDuration,
+                                              "getMinEnvelopeEffectControlPointDuration");
+        logFailure<std::chrono::milliseconds>(maxEnvelopeEffectControlPointDuration,
+                                              "getMaxEnvelopeEffectControlPointDuration");
     }
 
     bool shouldRetry() const {
@@ -274,7 +293,10 @@
                 pwlePrimitiveDurationMax.shouldRetry() || compositionSizeMax.shouldRetry() ||
                 pwleSizeMax.shouldRetry() || minFrequency.shouldRetry() ||
                 resonantFrequency.shouldRetry() || frequencyResolution.shouldRetry() ||
-                qFactor.shouldRetry() || maxAmplitudes.shouldRetry();
+                qFactor.shouldRetry() || maxAmplitudes.shouldRetry() ||
+                maxEnvelopeEffectSize.shouldRetry() ||
+                minEnvelopeEffectControlPointDuration.shouldRetry() ||
+                maxEnvelopeEffectControlPointDuration.shouldRetry();
     }
 
 private:
@@ -302,19 +324,22 @@
                 mResonantFrequency,
                 mFrequencyResolution,
                 mQFactor,
-                mMaxAmplitudes};
+                mMaxAmplitudes,
+                mMaxEnvelopeEffectSize,
+                mMinEnvelopeEffectControlPointDuration,
+                mMaxEnvelopeEffectControlPointDuration};
     }
 
 private:
     // Create a transaction failed results as default so we can retry on the first time we get them.
     static const constexpr char* MSG = "never loaded";
     HalResult<Capabilities> mCapabilities = HalResult<Capabilities>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::Effect>> mSupportedEffects =
-            HalResult<std::vector<hardware::vibrator::Effect>>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::Braking>> mSupportedBraking =
-            HalResult<std::vector<hardware::vibrator::Braking>>::transactionFailed(MSG);
-    HalResult<std::vector<hardware::vibrator::CompositePrimitive>> mSupportedPrimitives =
-            HalResult<std::vector<hardware::vibrator::CompositePrimitive>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::Effect>> mSupportedEffects =
+            HalResult<std::vector<Info::Effect>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::Braking>> mSupportedBraking =
+            HalResult<std::vector<Info::Braking>>::transactionFailed(MSG);
+    HalResult<std::vector<Info::CompositePrimitive>> mSupportedPrimitives =
+            HalResult<std::vector<Info::CompositePrimitive>>::transactionFailed(MSG);
     HalResult<std::vector<std::chrono::milliseconds>> mPrimitiveDurations =
             HalResult<std::vector<std::chrono::milliseconds>>::transactionFailed(MSG);
     HalResult<std::chrono::milliseconds> mPrimitiveDelayMax =
@@ -329,6 +354,11 @@
     HalResult<float> mQFactor = HalResult<float>::transactionFailed(MSG);
     HalResult<std::vector<float>> mMaxAmplitudes =
             HalResult<std::vector<float>>::transactionFailed(MSG);
+    HalResult<int32_t> mMaxEnvelopeEffectSize = HalResult<int>::transactionFailed(MSG);
+    HalResult<std::chrono::milliseconds> mMinEnvelopeEffectControlPointDuration =
+            HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
+    HalResult<std::chrono::milliseconds> mMaxEnvelopeEffectControlPointDuration =
+            HalResult<std::chrono::milliseconds>::transactionFailed(MSG);
 
     friend class HalWrapper;
 };
@@ -336,6 +366,16 @@
 // Wrapper for Vibrator HAL handlers.
 class HalWrapper {
 public:
+    using Effect = aidl::android::hardware::vibrator::Effect;
+    using EffectStrength = aidl::android::hardware::vibrator::EffectStrength;
+    using VendorEffect = aidl::android::hardware::vibrator::VendorEffect;
+    using CompositePrimitive = aidl::android::hardware::vibrator::CompositePrimitive;
+    using CompositeEffect = aidl::android::hardware::vibrator::CompositeEffect;
+    using Braking = aidl::android::hardware::vibrator::Braking;
+    using PrimitivePwle = aidl::android::hardware::vibrator::PrimitivePwle;
+    using PwleV2Primitive = aidl::android::hardware::vibrator::PwleV2Primitive;
+    using PwleV2OutputMapEntry = aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
+
     explicit HalWrapper(std::shared_ptr<CallbackScheduler> scheduler)
           : mCallbackScheduler(std::move(scheduler)) {}
     virtual ~HalWrapper() = default;
@@ -355,21 +395,25 @@
     virtual HalResult<void> setAmplitude(float amplitude) = 0;
     virtual HalResult<void> setExternalControl(bool enabled) = 0;
 
-    virtual HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                           hardware::vibrator::EffectStrength strength) = 0;
+    virtual HalResult<void> alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) = 0;
     virtual HalResult<void> alwaysOnDisable(int32_t id) = 0;
 
     virtual HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            Effect effect, EffectStrength strength,
             const std::function<void()>& completionCallback) = 0;
 
+    virtual HalResult<void> performVendorEffect(const VendorEffect& effect,
+                                                const std::function<void()>& completionCallback);
+
     virtual HalResult<std::chrono::milliseconds> performComposedEffect(
-            const std::vector<hardware::vibrator::CompositeEffect>& primitives,
+            const std::vector<CompositeEffect>& primitives,
             const std::function<void()>& completionCallback);
 
-    virtual HalResult<void> performPwleEffect(
-            const std::vector<hardware::vibrator::PrimitivePwle>& primitives,
-            const std::function<void()>& completionCallback);
+    virtual HalResult<void> performPwleEffect(const std::vector<PrimitivePwle>& primitives,
+                                              const std::function<void()>& completionCallback);
+
+    virtual HalResult<void> composePwleV2(const std::vector<PwleV2Primitive>& composite,
+                                          const std::function<void()>& completionCallback);
 
 protected:
     // Shared pointer to allow CallbackScheduler to outlive this wrapper.
@@ -381,12 +425,11 @@
 
     // Request vibrator info to HAL skipping cache.
     virtual HalResult<Capabilities> getCapabilitiesInternal() = 0;
-    virtual HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal();
-    virtual HalResult<std::vector<hardware::vibrator::Braking>> getSupportedBrakingInternal();
-    virtual HalResult<std::vector<hardware::vibrator::CompositePrimitive>>
-    getSupportedPrimitivesInternal();
+    virtual HalResult<std::vector<Effect>> getSupportedEffectsInternal();
+    virtual HalResult<std::vector<Braking>> getSupportedBrakingInternal();
+    virtual HalResult<std::vector<CompositePrimitive>> getSupportedPrimitivesInternal();
     virtual HalResult<std::vector<std::chrono::milliseconds>> getPrimitiveDurationsInternal(
-            const std::vector<hardware::vibrator::CompositePrimitive>& supportedPrimitives);
+            const std::vector<CompositePrimitive>& supportedPrimitives);
     virtual HalResult<std::chrono::milliseconds> getPrimitiveDelayMaxInternal();
     virtual HalResult<std::chrono::milliseconds> getPrimitiveDurationMaxInternal();
     virtual HalResult<int32_t> getCompositionSizeMaxInternal();
@@ -396,6 +439,9 @@
     virtual HalResult<float> getFrequencyResolutionInternal();
     virtual HalResult<float> getQFactorInternal();
     virtual HalResult<std::vector<float>> getMaxAmplitudesInternal();
+    virtual HalResult<int32_t> getMaxEnvelopeEffectSizeInternal();
+    virtual HalResult<std::chrono::milliseconds> getMinEnvelopeEffectControlPointDurationInternal();
+    virtual HalResult<std::chrono::milliseconds> getMaxEnvelopeEffectControlPointDurationInternal();
 
 private:
     std::mutex mInfoMutex;
@@ -405,12 +451,17 @@
 // Wrapper for the AIDL Vibrator HAL.
 class AidlHalWrapper : public HalWrapper {
 public:
+    using IVibrator = aidl::android::hardware::vibrator::IVibrator;
+    using reconnect_fn = std::function<HalResult<std::shared_ptr<IVibrator>>()>;
+
     AidlHalWrapper(
-            std::shared_ptr<CallbackScheduler> scheduler, sp<hardware::vibrator::IVibrator> handle,
-            std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> reconnectFn =
+            std::shared_ptr<CallbackScheduler> scheduler, std::shared_ptr<IVibrator> handle,
+            reconnect_fn reconnectFn =
                     []() {
-                        return HalResult<sp<hardware::vibrator::IVibrator>>::ok(
-                                checkVintfService<hardware::vibrator::IVibrator>());
+                        auto serviceName = std::string(IVibrator::descriptor) + "/default";
+                        auto hal = IVibrator::fromBinder(
+                                ndk::SpAIBinder(AServiceManager_checkService(serviceName.c_str())));
+                        return HalResult<std::shared_ptr<IVibrator>>::ok(std::move(hal));
                     })
           : HalWrapper(std::move(scheduler)),
             mReconnectFn(reconnectFn),
@@ -427,32 +478,36 @@
     HalResult<void> setAmplitude(float amplitude) override final;
     HalResult<void> setExternalControl(bool enabled) override final;
 
-    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnEnable(int32_t id, Effect effect,
+                                   EffectStrength strength) override final;
     HalResult<void> alwaysOnDisable(int32_t id) override final;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            Effect effect, EffectStrength strength,
+            const std::function<void()>& completionCallback) override final;
+
+    HalResult<void> performVendorEffect(
+            const VendorEffect& effect,
             const std::function<void()>& completionCallback) override final;
 
     HalResult<std::chrono::milliseconds> performComposedEffect(
-            const std::vector<hardware::vibrator::CompositeEffect>& primitives,
+            const std::vector<CompositeEffect>& primitives,
             const std::function<void()>& completionCallback) override final;
 
     HalResult<void> performPwleEffect(
-            const std::vector<hardware::vibrator::PrimitivePwle>& primitives,
+            const std::vector<PrimitivePwle>& primitives,
             const std::function<void()>& completionCallback) override final;
 
+    HalResult<void> composePwleV2(const std::vector<PwleV2Primitive>& composite,
+                                  const std::function<void()>& completionCallback) override final;
+
 protected:
     HalResult<Capabilities> getCapabilitiesInternal() override final;
-    HalResult<std::vector<hardware::vibrator::Effect>> getSupportedEffectsInternal() override final;
-    HalResult<std::vector<hardware::vibrator::Braking>> getSupportedBrakingInternal()
-            override final;
-    HalResult<std::vector<hardware::vibrator::CompositePrimitive>> getSupportedPrimitivesInternal()
-            override final;
+    HalResult<std::vector<Effect>> getSupportedEffectsInternal() override final;
+    HalResult<std::vector<Braking>> getSupportedBrakingInternal() override final;
+    HalResult<std::vector<CompositePrimitive>> getSupportedPrimitivesInternal() override final;
     HalResult<std::vector<std::chrono::milliseconds>> getPrimitiveDurationsInternal(
-            const std::vector<hardware::vibrator::CompositePrimitive>& supportedPrimitives)
-            override final;
+            const std::vector<CompositePrimitive>& supportedPrimitives) override final;
     HalResult<std::chrono::milliseconds> getPrimitiveDelayMaxInternal() override final;
     HalResult<std::chrono::milliseconds> getPrimitiveDurationMaxInternal() override final;
     HalResult<int32_t> getCompositionSizeMaxInternal() override final;
@@ -462,13 +517,20 @@
     HalResult<float> getFrequencyResolutionInternal() override final;
     HalResult<float> getQFactorInternal() override final;
     HalResult<std::vector<float>> getMaxAmplitudesInternal() override final;
+    HalResult<int32_t> getMaxEnvelopeEffectSizeInternal() override final;
+
+    HalResult<std::chrono::milliseconds> getMinEnvelopeEffectControlPointDurationInternal()
+            override final;
+
+    HalResult<std::chrono::milliseconds> getMaxEnvelopeEffectControlPointDurationInternal()
+            override final;
 
 private:
-    const std::function<HalResult<sp<hardware::vibrator::IVibrator>>()> mReconnectFn;
+    const reconnect_fn mReconnectFn;
     std::mutex mHandleMutex;
-    sp<hardware::vibrator::IVibrator> mHandle GUARDED_BY(mHandleMutex);
+    std::shared_ptr<IVibrator> mHandle GUARDED_BY(mHandleMutex);
 
-    sp<hardware::vibrator::IVibrator> getHal();
+    std::shared_ptr<IVibrator> getHal();
 };
 
 // Wrapper for the HDIL Vibrator HALs.
@@ -489,8 +551,8 @@
     HalResult<void> setAmplitude(float amplitude) override final;
     virtual HalResult<void> setExternalControl(bool enabled) override;
 
-    HalResult<void> alwaysOnEnable(int32_t id, hardware::vibrator::Effect effect,
-                                   hardware::vibrator::EffectStrength strength) override final;
+    HalResult<void> alwaysOnEnable(int32_t id, HalWrapper::Effect effect,
+                                   HalWrapper::EffectStrength strength) override final;
     HalResult<void> alwaysOnDisable(int32_t id) override final;
 
 protected:
@@ -506,8 +568,7 @@
 
     template <class T>
     HalResult<std::chrono::milliseconds> performInternal(
-            perform_fn<T> performFn, sp<I> handle, T effect,
-            hardware::vibrator::EffectStrength strength,
+            perform_fn<T> performFn, sp<I> handle, T effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback);
 
     sp<I> getHal();
@@ -523,7 +584,7 @@
     virtual ~HidlHalWrapperV1_0() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -537,7 +598,7 @@
     virtual ~HidlHalWrapperV1_1() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -551,7 +612,7 @@
     virtual ~HidlHalWrapperV1_2() = default;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 };
 
@@ -567,7 +628,7 @@
     HalResult<void> setExternalControl(bool enabled) override final;
 
     HalResult<std::chrono::milliseconds> performEffect(
-            hardware::vibrator::Effect effect, hardware::vibrator::EffectStrength strength,
+            HalWrapper::Effect effect, HalWrapper::EffectStrength strength,
             const std::function<void()>& completionCallback) override final;
 
 protected:
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
index 9168565..70c846b 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalController.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
 #define ANDROID_OS_VIBRATOR_MANAGER_HAL_CONTROLLER_H
 
-#include <android/hardware/vibrator/IVibratorManager.h>
+#include <aidl/android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 #include <unordered_map>
diff --git a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
index 563f55e..9e3f221 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorManagerHalWrapper.h
@@ -17,7 +17,7 @@
 #ifndef ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 #define ANDROID_OS_VIBRATOR_MANAGER_HAL_WRAPPER_H
 
-#include <android/hardware/vibrator/IVibratorManager.h>
+#include <aidl/android/hardware/vibrator/IVibratorManager.h>
 #include <vibratorservice/VibratorHalController.h>
 #include <unordered_map>
 
@@ -28,14 +28,17 @@
 // VibratorManager HAL capabilities.
 enum class ManagerCapabilities : int32_t {
     NONE = 0,
-    SYNC = hardware::vibrator::IVibratorManager::CAP_SYNC,
-    PREPARE_ON = hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
-    PREPARE_PERFORM = hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
-    PREPARE_COMPOSE = hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
-    MIXED_TRIGGER_ON = hardware::vibrator::IVibratorManager::IVibratorManager::CAP_MIXED_TRIGGER_ON,
-    MIXED_TRIGGER_PERFORM = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
-    MIXED_TRIGGER_COMPOSE = hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
-    TRIGGER_CALLBACK = hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
+    SYNC = aidl::android::hardware::vibrator::IVibratorManager::CAP_SYNC,
+    PREPARE_ON = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_ON,
+    PREPARE_PERFORM = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_PERFORM,
+    PREPARE_COMPOSE = aidl::android::hardware::vibrator::IVibratorManager::CAP_PREPARE_COMPOSE,
+    MIXED_TRIGGER_ON = aidl::android::hardware::vibrator::IVibratorManager::IVibratorManager::
+            CAP_MIXED_TRIGGER_ON,
+    MIXED_TRIGGER_PERFORM =
+            aidl::android::hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_PERFORM,
+    MIXED_TRIGGER_COMPOSE =
+            aidl::android::hardware::vibrator::IVibratorManager::CAP_MIXED_TRIGGER_COMPOSE,
+    TRIGGER_CALLBACK = aidl::android::hardware::vibrator::IVibratorManager::CAP_TRIGGER_CALLBACK
 };
 
 inline ManagerCapabilities operator|(ManagerCapabilities lhs, ManagerCapabilities rhs) {
@@ -106,8 +109,10 @@
 // Wrapper for the AIDL VibratorManager HAL.
 class AidlManagerHalWrapper : public ManagerHalWrapper {
 public:
+    using VibratorManager = aidl::android::hardware::vibrator::IVibratorManager;
+
     explicit AidlManagerHalWrapper(std::shared_ptr<CallbackScheduler> callbackScheduler,
-                                   sp<hardware::vibrator::IVibratorManager> handle)
+                                   std::shared_ptr<VibratorManager> handle)
           : mHandle(std::move(handle)), mCallbackScheduler(callbackScheduler) {}
     virtual ~AidlManagerHalWrapper() = default;
 
@@ -126,14 +131,14 @@
     std::mutex mHandleMutex;
     std::mutex mCapabilitiesMutex;
     std::mutex mVibratorsMutex;
-    sp<hardware::vibrator::IVibratorManager> mHandle GUARDED_BY(mHandleMutex);
+    std::shared_ptr<VibratorManager> mHandle GUARDED_BY(mHandleMutex);
     std::optional<ManagerCapabilities> mCapabilities GUARDED_BY(mCapabilitiesMutex);
     std::optional<std::vector<int32_t>> mVibratorIds GUARDED_BY(mVibratorsMutex);
     std::unordered_map<int32_t, std::shared_ptr<HalController>> mVibrators
             GUARDED_BY(mVibratorsMutex);
     std::shared_ptr<CallbackScheduler> mCallbackScheduler;
 
-    sp<hardware::vibrator::IVibratorManager> getHal();
+    std::shared_ptr<VibratorManager> getHal();
     std::shared_ptr<HalWrapper> connectToVibrator(int32_t vibratorId,
                                                   std::shared_ptr<CallbackScheduler> scheduler);
 };
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index be71dc2..92527eb 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -44,12 +44,12 @@
     ],
     shared_libs: [
         "libbase",
-        "libbinder",
+        "libbinder_ndk",
         "libhidlbase",
         "liblog",
         "libvibratorservice",
         "libutils",
-        "android.hardware.vibrator-V2-cpp",
+        "android.hardware.vibrator-V3-ndk",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
         "android.hardware.vibrator@1.2",
diff --git a/services/vibratorservice/test/VibratorHalControllerTest.cpp b/services/vibratorservice/test/VibratorHalControllerTest.cpp
index 9b95d74..f4c2898 100644
--- a/services/vibratorservice/test/VibratorHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorHalControllerTest.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalControllerTest"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 #include <cutils/atomic.h>
 
 #include <gmock/gmock.h>
@@ -29,10 +29,11 @@
 #include <vibratorservice/VibratorHalController.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -46,41 +47,12 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockHalWrapper : public vibrator::HalWrapper {
-public:
-    MockHalWrapper(std::shared_ptr<vibrator::CallbackScheduler> scheduler)
-          : HalWrapper(scheduler) {}
-    virtual ~MockHalWrapper() = default;
-
-    MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
-    MOCK_METHOD(void, tryReconnect, (), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, on,
-                (milliseconds timeout, const std::function<void()>& completionCallback),
-                (override));
-    MOCK_METHOD(vibrator::HalResult<void>, off, (), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnEnable,
-                (int32_t id, Effect effect, EffectStrength strength), (override));
-    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(vibrator::HalResult<milliseconds>, performEffect,
-                (Effect effect, EffectStrength strength,
-                 const std::function<void()>& completionCallback),
-                (override));
-    MOCK_METHOD(vibrator::HalResult<vibrator::Capabilities>, getCapabilitiesInternal, (),
-                (override));
-
-    vibrator::CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorHalControllerTest : public Test {
 public:
     void SetUp() override {
         mConnectCounter = 0;
         auto callbackScheduler = std::make_shared<vibrator::CallbackScheduler>();
-        mMockHal = std::make_shared<StrictMock<MockHalWrapper>>(callbackScheduler);
+        mMockHal = std::make_shared<StrictMock<vibrator::MockHalWrapper>>(callbackScheduler);
         mController = std::make_unique<
                 vibrator::HalController>(std::move(callbackScheduler),
                                          [&](std::shared_ptr<vibrator::CallbackScheduler>) {
@@ -92,7 +64,7 @@
 
 protected:
     int32_t mConnectCounter;
-    std::shared_ptr<MockHalWrapper> mMockHal;
+    std::shared_ptr<vibrator::MockHalWrapper> mMockHal;
     std::unique_ptr<vibrator::HalController> mController;
 };
 
@@ -255,16 +227,17 @@
                 .WillRepeatedly(Return(vibrator::HalResult<void>::transactionFailed("message")));
     }
 
-    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
-    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+    auto counter = vibrator::TestCounter(0);
 
-    auto onFn = [&](vibrator::HalWrapper* hal) { return hal->on(10ms, callback); };
+    auto onFn = [&](vibrator::HalWrapper* hal) {
+        return hal->on(10ms, [&counter] { counter.increment(); });
+    };
     ASSERT_TRUE(mController->doWithRetry<void>(onFn, "on").isOk());
     ASSERT_TRUE(mController->doWithRetry<void>(PING_FN, "ping").isFailed());
     mMockHal.reset();
-    ASSERT_EQ(0, *callbackCounter.get());
+    ASSERT_EQ(0, counter.get());
 
     // Callback triggered even after HalWrapper was reconnected.
-    std::this_thread::sleep_for(15ms);
-    ASSERT_EQ(1, *callbackCounter.get());
+    counter.tryWaitUntilCountIsAtLeast(1, 500ms);
+    ASSERT_EQ(1, counter.get());
 }
diff --git a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
index 03c9e77..17f384d 100644
--- a/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperAidlTest.cpp
@@ -16,7 +16,8 @@
 
 #define LOG_TAG "VibratorHalWrapperAidlTest"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/persistable_bundle_aidl.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,18 +28,20 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::binder::Status;
-
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::IVibratorCallback;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::PwleV2Primitive;
+using aidl::android::hardware::vibrator::VendorEffect;
+using aidl::android::os::PersistableBundle;
 
 using namespace android;
 using namespace std::chrono_literals;
@@ -46,61 +49,10 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockBinder : public BBinder {
-public:
-    MOCK_METHOD(status_t, linkToDeath,
-                (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
-    MOCK_METHOD(status_t, unlinkToDeath,
-                (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
-                 wp<DeathRecipient>* outRecipient),
-                (override));
-    MOCK_METHOD(status_t, pingBinder, (), (override));
-};
-
-class MockIVibrator : public IVibrator {
-public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, off, (), (override));
-    MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, perform,
-                (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
-                (override));
-    MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
-                (override));
-    MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
-    MOCK_METHOD(Status, compose,
-                (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
-                (override));
-    MOCK_METHOD(Status, composePwle,
-                (const std::vector<PrimitivePwle>& e, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
-    MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
-    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
-    MOCK_METHOD(Status, getFrequencyResolution, (float* ret), (override));
-    MOCK_METHOD(Status, getFrequencyMinimum, (float* ret), (override));
-    MOCK_METHOD(Status, getBandwidthAmplitudeMap, (std::vector<float> * ret), (override));
-    MOCK_METHOD(Status, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getPwleCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedBraking, (std::vector<Braking> * ret), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorHalWrapperAidlTest : public Test {
 public:
     void SetUp() override {
-        mMockBinder = new StrictMock<MockBinder>();
-        mMockHal = new StrictMock<MockIVibrator>();
+        mMockHal = ndk::SharedRefBase::make<StrictMock<vibrator::MockIVibrator>>();
         mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
         mWrapper = std::make_unique<vibrator::AidlHalWrapper>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
@@ -109,54 +61,28 @@
 protected:
     std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::HalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIVibrator>> mMockHal = nullptr;
-    sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockIVibrator>> mMockHal = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
 
-ACTION(TriggerCallbackInArg1) {
-    if (arg1 != nullptr) {
-        arg1->onComplete();
-    }
-}
-
-ACTION(TriggerCallbackInArg2) {
-    if (arg2 != nullptr) {
-        arg2->onComplete();
-    }
-}
-
-TEST_F(VibratorHalWrapperAidlTest, TestPing) {
-    EXPECT_CALL(*mMockHal.get(), onAsBinder())
-            .Times(Exactly(2))
-            .WillRepeatedly(Return(mMockBinder.get()));
-    EXPECT_CALL(*mMockBinder.get(), pingBinder())
-            .Times(Exactly(2))
-            .WillOnce(Return(android::OK))
-            .WillRepeatedly(Return(android::DEAD_OBJECT));
-
-    ASSERT_TRUE(mWrapper->ping().isOk());
-    ASSERT_TRUE(mWrapper->ping().isFailed());
-}
-
 TEST_F(VibratorHalWrapperAidlTest, TestOnWithCallbackSupport) {
     {
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(100), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), on(Eq(1000), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -179,20 +105,20 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_COMPOSE_EFFECTS), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_COMPOSE_EFFECTS),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), on(Eq(10), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status()));
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
                 .Times(Exactly(1))
-                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+                .WillOnce(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), on(Eq(11), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), on(Eq(12), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -211,10 +137,9 @@
 TEST_F(VibratorHalWrapperAidlTest, TestOff) {
     EXPECT_CALL(*mMockHal.get(), off())
             .Times(Exactly(3))
-            .WillOnce(Return(Status()))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+            .WillOnce(Return(ndk::ScopedAStatus::ok()))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
 
     ASSERT_TRUE(mWrapper->off().isOk());
     ASSERT_TRUE(mWrapper->off().isUnsupported());
@@ -224,13 +149,15 @@
 TEST_F(VibratorHalWrapperAidlTest, TestSetAmplitude) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.1f))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.1f)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.2f)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), setAmplitude(Eq(0.5f)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->setAmplitude(0.1f).isOk());
@@ -241,12 +168,13 @@
 TEST_F(VibratorHalWrapperAidlTest, TestSetExternalControl) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(true)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), setExternalControl(Eq(false)))
                 .Times(Exactly(2))
-                .WillOnce(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->setExternalControl(true).isOk());
@@ -259,15 +187,16 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(1), Eq(Effect::CLICK), Eq(EffectStrength::LIGHT)))
-                .Times(Exactly(1));
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(2), Eq(Effect::TICK), Eq(EffectStrength::MEDIUM)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(),
                     alwaysOnEnable(Eq(3), Eq(Effect::POP), Eq(EffectStrength::STRONG)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     auto result = mWrapper->alwaysOnEnable(1, Effect::CLICK, EffectStrength::LIGHT);
@@ -281,14 +210,15 @@
 TEST_F(VibratorHalWrapperAidlTest, TestAlwaysOnDisable) {
     {
         InSequence seq;
-        EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1))).Times(Exactly(1));
+        EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(1)))
+                .Times(Exactly(1))
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
         EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(2)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), alwaysOnDisable(Eq(3)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     ASSERT_TRUE(mWrapper->alwaysOnDisable(1).isOk());
@@ -305,72 +235,94 @@
     constexpr int32_t PWLE_SIZE_MAX = 20;
     constexpr int32_t PRIMITIVE_DELAY_MAX = 100;
     constexpr int32_t PWLE_DURATION_MAX = 200;
+    constexpr int32_t PWLE_V2_COMPOSITION_SIZE_MAX = 16;
+    constexpr int32_t PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS = 20;
+    constexpr int32_t PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS = 1000;
     std::vector<Effect> supportedEffects = {Effect::CLICK, Effect::TICK};
     std::vector<CompositePrimitive> supportedPrimitives = {CompositePrimitive::CLICK};
     std::vector<Braking> supportedBraking = {Braking::CLAB};
     std::vector<float> amplitudes = {0.f, 1.f, 0.f};
 
     std::vector<std::chrono::milliseconds> primitiveDurations;
-    constexpr auto primitiveRange = enum_range<CompositePrimitive>();
+    constexpr auto primitiveRange = ndk::enum_range<CompositePrimitive>();
     constexpr auto primitiveCount = std::distance(primitiveRange.begin(), primitiveRange.end());
     primitiveDurations.resize(primitiveCount);
     primitiveDurations[static_cast<size_t>(CompositePrimitive::CLICK)] = 10ms;
 
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(supportedEffects), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedBraking(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedBraking), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(supportedBraking), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(supportedPrimitives), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(10), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<1>(10), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionSizeMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionDelayMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwlePrimitiveDurationMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwleCompositionSizeMax(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyMinimum(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_MIN), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F_MIN), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getResonantFrequency(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F0), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F0), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyResolution(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_RESOLUTION), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(F_RESOLUTION), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getQFactor(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(Q_FACTOR), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(Q_FACTOR), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getBandwidthAmplitudeMap(_))
             .Times(Exactly(2))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(amplitudes), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(amplitudes), Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2CompositionSizeMax(_))
+            .Times(Exactly(2))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2PrimitiveDurationMinMillis(_))
+            .Times(Exactly(2))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS),
+                            Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2PrimitiveDurationMaxMillis(_))
+            .Times(Exactly(2))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
+                            Return(ndk::ScopedAStatus::ok())));
 
     vibrator::Info failed = mWrapper->getInfo();
     ASSERT_TRUE(failed.capabilities.isFailed());
@@ -387,6 +339,9 @@
     ASSERT_TRUE(failed.frequencyResolution.isFailed());
     ASSERT_TRUE(failed.qFactor.isFailed());
     ASSERT_TRUE(failed.maxAmplitudes.isFailed());
+    ASSERT_TRUE(failed.maxEnvelopeEffectSize.isFailed());
+    ASSERT_TRUE(failed.minEnvelopeEffectControlPointDuration.isFailed());
+    ASSERT_TRUE(failed.maxEnvelopeEffectControlPointDuration.isFailed());
 
     vibrator::Info successful = mWrapper->getInfo();
     ASSERT_EQ(vibrator::Capabilities::ON_CALLBACK, successful.capabilities.value());
@@ -404,6 +359,11 @@
     ASSERT_EQ(F_RESOLUTION, successful.frequencyResolution.value());
     ASSERT_EQ(Q_FACTOR, successful.qFactor.value());
     ASSERT_EQ(amplitudes, successful.maxAmplitudes.value());
+    ASSERT_EQ(PWLE_V2_COMPOSITION_SIZE_MAX, successful.maxEnvelopeEffectSize.value());
+    ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS),
+              successful.minEnvelopeEffectControlPointDuration.value());
+    ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
+              successful.maxEnvelopeEffectControlPointDuration.value());
 }
 
 TEST_F(VibratorHalWrapperAidlTest, TestGetInfoCachesResult) {
@@ -413,50 +373,65 @@
     constexpr int32_t PWLE_SIZE_MAX = 20;
     constexpr int32_t PRIMITIVE_DELAY_MAX = 100;
     constexpr int32_t PWLE_DURATION_MAX = 200;
+    constexpr int32_t PWLE_V2_COMPOSITION_SIZE_MAX = 16;
+    constexpr int32_t PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS = 20;
+    constexpr int32_t PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS = 1000;
     std::vector<Effect> supportedEffects = {Effect::CLICK, Effect::TICK};
 
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getSupportedEffects(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(supportedEffects), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(supportedEffects), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getQFactor(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
             .Times(Exactly(1))
-            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getCompositionSizeMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getCompositionDelayMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(Status())));
+            .WillOnce(
+                    DoAll(SetArgPointee<0>(PRIMITIVE_DELAY_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwlePrimitiveDurationMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_DURATION_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getPwleCompositionSizeMax(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_SIZE_MAX), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyMinimum(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F_MIN), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(F_MIN), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getResonantFrequency(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(F0), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(F0), Return(ndk::ScopedAStatus::ok())));
     EXPECT_CALL(*mMockHal.get(), getFrequencyResolution(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
     EXPECT_CALL(*mMockHal.get(), getBandwidthAmplitudeMap(_))
             .Times(Exactly(1))
-            .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
     EXPECT_CALL(*mMockHal.get(), getSupportedBraking(_))
             .Times(Exactly(1))
-            .WillRepeatedly(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2CompositionSizeMax(_))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_COMPOSITION_SIZE_MAX),
+                            Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2PrimitiveDurationMinMillis(_))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS),
+                            Return(ndk::ScopedAStatus::ok())));
+    EXPECT_CALL(*mMockHal.get(), getPwleV2PrimitiveDurationMaxMillis(_))
+            .Times(Exactly(1))
+            .WillOnce(DoAll(SetArgPointee<0>(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
+                            Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -480,6 +455,11 @@
     ASSERT_TRUE(info.frequencyResolution.isUnsupported());
     ASSERT_TRUE(info.qFactor.isUnsupported());
     ASSERT_TRUE(info.maxAmplitudes.isUnsupported());
+    ASSERT_EQ(PWLE_V2_COMPOSITION_SIZE_MAX, info.maxEnvelopeEffectSize.value());
+    ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MAX_ALLOWED_PRIMITIVE_MIN_DURATION_MS),
+              info.minEnvelopeEffectControlPointDuration.value());
+    ASSERT_EQ(std::chrono::milliseconds(PWLE_V2_MIN_REQUIRED_PRIMITIVE_MAX_DURATION_MS),
+              info.maxEnvelopeEffectControlPointDuration.value());
 }
 
 TEST_F(VibratorHalWrapperAidlTest, TestPerformEffectWithCallbackSupport) {
@@ -487,18 +467,18 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_PERFORM_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_PERFORM_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<3>(1000), TriggerCallbackInArg2(), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<3>(1000), WithArg<2>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -525,21 +505,20 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibrator::CAP_ON_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::CLICK), Eq(EffectStrength::LIGHT), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<3>(10), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<3>(10), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockScheduler.get(), schedule(_, Eq(10ms)))
                 .Times(Exactly(1))
-                .WillRepeatedly(vibrator::TriggerSchedulerCallback());
+                .WillOnce(vibrator::TriggerSchedulerCallback());
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::POP), Eq(EffectStrength::MEDIUM), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), perform(Eq(Effect::THUD), Eq(EffectStrength::STRONG), _, _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -560,6 +539,42 @@
     ASSERT_EQ(1, *callbackCounter.get());
 }
 
+TEST_F(VibratorHalWrapperAidlTest, TestPerformVendorEffect) {
+    PersistableBundle vendorData;
+    vendorData.putInt("key", 1);
+    VendorEffect vendorEffect;
+    vendorEffect.vendorData = vendorData;
+    vendorEffect.strength = EffectStrength::MEDIUM;
+    vendorEffect.scale = 0.5f;
+
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), performVendorEffect(_, _))
+                .Times(Exactly(3))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    auto result = mWrapper->performVendorEffect(vendorEffect, callback);
+    ASSERT_TRUE(result.isUnsupported());
+    // Callback not triggered on failure
+    ASSERT_EQ(0, *callbackCounter.get());
+
+    result = mWrapper->performVendorEffect(vendorEffect, callback);
+    ASSERT_TRUE(result.isFailed());
+    // Callback not triggered for unsupported
+    ASSERT_EQ(0, *callbackCounter.get());
+
+    result = mWrapper->performVendorEffect(vendorEffect, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(1, *callbackCounter.get());
+}
+
 TEST_F(VibratorHalWrapperAidlTest, TestPerformComposedEffect) {
     std::vector<CompositePrimitive> supportedPrimitives = {CompositePrimitive::CLICK,
                                                            CompositePrimitive::SPIN,
@@ -576,26 +591,28 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitives),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::CLICK), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(1), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(1), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(3), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(3), Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), compose(Eq(emptyEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(singleEffect), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -630,26 +647,32 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getSupportedPrimitives(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(supportedPrimitives), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitives),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::SPIN), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), getPrimitiveDuration(Eq(CompositePrimitive::THUD), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(2), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(2), Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), compose(Eq(multipleEffects), _))
                 .Times(Exactly(2))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -680,12 +703,12 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), composePwle(Eq(emptyPrimitives), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(
-                        Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)));
         EXPECT_CALL(*mMockHal.get(), composePwle(Eq(multiplePrimitives), _))
                 .Times(Exactly(2))
-                .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-                .WillRepeatedly(DoAll(TriggerCallbackInArg1(), Return(Status())));
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -705,3 +728,38 @@
     ASSERT_TRUE(result.isOk());
     ASSERT_EQ(1, *callbackCounter.get());
 }
+
+TEST_F(VibratorHalWrapperAidlTest, TestComposePwleV2) {
+    auto pwleEffect = {
+            PwleV2Primitive(/*amplitude=*/0.2, /*frequency=*/50, /*time=*/100),
+            PwleV2Primitive(/*amplitude=*/0.5, /*frequency=*/150, /*time=*/100),
+            PwleV2Primitive(/*amplitude=*/0.8, /*frequency=*/250, /*time=*/100),
+    };
+
+    {
+        InSequence seq;
+        EXPECT_CALL(*mMockHal.get(), composePwleV2(_, _))
+                .Times(Exactly(3))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(WithArg<1>(vibrator::TriggerCallback()),
+                                Return(ndk::ScopedAStatus::ok())));
+    }
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    auto result = mWrapper->composePwleV2(pwleEffect, callback);
+    ASSERT_TRUE(result.isUnsupported());
+    // Callback not triggered on failure
+    ASSERT_EQ(0, *callbackCounter.get());
+
+    result = mWrapper->composePwleV2(pwleEffect, callback);
+    ASSERT_TRUE(result.isFailed());
+    // Callback not triggered for unsupported
+    ASSERT_EQ(0, *callbackCounter.get());
+
+    result = mWrapper->composePwleV2(pwleEffect, callback);
+    ASSERT_TRUE(result.isOk());
+    ASSERT_EQ(1, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
index 0c27fc7..a09ddec 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_0Test.cpp
@@ -16,7 +16,8 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_0Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+#include <android/persistable_bundle_aidl.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,17 +28,21 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::PwleV2Primitive;
+using aidl::android::hardware::vibrator::VendorEffect;
+using aidl::android::os::PersistableBundle;
 
 using namespace android;
 using namespace std::chrono_literals;
@@ -215,6 +220,9 @@
     ASSERT_TRUE(info.frequencyResolution.isUnsupported());
     ASSERT_TRUE(info.qFactor.isUnsupported());
     ASSERT_TRUE(info.maxAmplitudes.isUnsupported());
+    ASSERT_TRUE(info.maxEnvelopeEffectSize.isUnsupported());
+    ASSERT_TRUE(info.minEnvelopeEffectControlPointDuration.isUnsupported());
+    ASSERT_TRUE(info.maxEnvelopeEffectControlPointDuration.isUnsupported());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestGetInfoWithoutAmplitudeControl) {
@@ -248,6 +256,9 @@
     ASSERT_TRUE(info.frequencyResolution.isUnsupported());
     ASSERT_TRUE(info.qFactor.isUnsupported());
     ASSERT_TRUE(info.maxAmplitudes.isUnsupported());
+    ASSERT_TRUE(info.maxEnvelopeEffectSize.isUnsupported());
+    ASSERT_TRUE(info.minEnvelopeEffectControlPointDuration.isUnsupported());
+    ASSERT_TRUE(info.maxEnvelopeEffectControlPointDuration.isUnsupported());
 }
 
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformEffect) {
@@ -316,6 +327,22 @@
     ASSERT_EQ(0, *callbackCounter.get());
 }
 
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformVendorEffectUnsupported) {
+    PersistableBundle vendorData; // empty
+    VendorEffect vendorEffect;
+    vendorEffect.vendorData = vendorData;
+    vendorEffect.strength = EffectStrength::LIGHT;
+    vendorEffect.scale = 1.0f;
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->performVendorEffect(vendorEffect, callback).isUnsupported());
+
+    // No callback is triggered.
+    ASSERT_EQ(0, *callbackCounter.get());
+}
+
 TEST_F(VibratorHalWrapperHidlV1_0Test, TestPerformComposedEffectUnsupported) {
     std::vector<CompositeEffect> emptyEffects, singleEffect, multipleEffects;
     singleEffect.push_back(
@@ -349,3 +376,19 @@
     // No callback is triggered.
     ASSERT_EQ(0, *callbackCounter.get());
 }
+
+TEST_F(VibratorHalWrapperHidlV1_0Test, TestComposePwleV2Unsupported) {
+    auto pwleEffect = {
+            PwleV2Primitive(/*amplitude=*/0.2, /*frequency=*/50, /*time=*/100),
+            PwleV2Primitive(/*amplitude=*/0.5, /*frequency=*/150, /*time=*/100),
+            PwleV2Primitive(/*amplitude=*/0.8, /*frequency=*/250, /*time=*/100),
+    };
+
+    std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
+    auto callback = vibrator::TestFactory::createCountingCallback(callbackCounter.get());
+
+    ASSERT_TRUE(mWrapper->composePwleV2(pwleEffect, callback).isUnsupported());
+
+    // No callback is triggered.
+    ASSERT_EQ(0, *callbackCounter.get());
+}
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
index d887efc..b0a6537 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_1Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_1Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -26,13 +26,14 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
index 26d9350..dfe3fa0 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_2Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_2Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -26,14 +26,15 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
 namespace V1_1 = android::hardware::vibrator::V1_1;
 namespace V1_2 = android::hardware::vibrator::V1_2;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
index a6f1a74..8624332 100644
--- a/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
+++ b/services/vibratorservice/test/VibratorHalWrapperHidlV1_3Test.cpp
@@ -16,7 +16,7 @@
 
 #define LOG_TAG "VibratorHalWrapperHidlV1_3Test"
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -27,6 +27,7 @@
 #include <vibratorservice/VibratorCallbackScheduler.h>
 #include <vibratorservice/VibratorHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 namespace V1_0 = android::hardware::vibrator::V1_0;
@@ -34,9 +35,9 @@
 namespace V1_2 = android::hardware::vibrator::V1_2;
 namespace V1_3 = android::hardware::vibrator::V1_3;
 
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
 
 using namespace android;
 using namespace std::chrono_literals;
diff --git a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
index 11a8b66..c7214e0 100644
--- a/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalControllerTest.cpp
@@ -24,6 +24,7 @@
 
 #include <vibratorservice/VibratorManagerHalController.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
 using android::vibrator::HalController;
@@ -35,6 +36,8 @@
 static const std::vector<int32_t> VIBRATOR_IDS = {1, 2};
 static constexpr int VIBRATOR_ID = 1;
 
+// -------------------------------------------------------------------------------------------------
+
 class MockManagerHalWrapper : public vibrator::ManagerHalWrapper {
 public:
     MOCK_METHOD(void, tryReconnect, (), (override));
@@ -51,6 +54,8 @@
     MOCK_METHOD(vibrator::HalResult<void>, cancelSynced, (), (override));
 };
 
+// -------------------------------------------------------------------------------------------------
+
 class VibratorManagerHalControllerTest : public Test {
 public:
     void SetUp() override {
@@ -106,6 +111,8 @@
     }
 };
 
+// -------------------------------------------------------------------------------------------------
+
 TEST_F(VibratorManagerHalControllerTest, TestInit) {
     mController->init();
     ASSERT_EQ(1, mConnectCounter);
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index dffc281..764d9be 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -23,84 +23,42 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
+#include "test_mocks.h"
 #include "test_utils.h"
 
-using android::binder::Status;
-
-using android::hardware::vibrator::Braking;
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
-using android::hardware::vibrator::IVibrator;
-using android::hardware::vibrator::IVibratorCallback;
-using android::hardware::vibrator::IVibratorManager;
-using android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::IVibratorManager;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 using namespace android;
 using namespace testing;
 
 static const auto OFF_FN = [](vibrator::HalWrapper* hal) { return hal->off(); };
 
-class MockBinder : public BBinder {
-public:
-    MOCK_METHOD(status_t, linkToDeath,
-                (const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags), (override));
-    MOCK_METHOD(status_t, unlinkToDeath,
-                (const wp<DeathRecipient>& recipient, void* cookie, uint32_t flags,
-                 wp<DeathRecipient>* outRecipient),
-                (override));
-    MOCK_METHOD(status_t, pingBinder, (), (override));
-};
-
-class MockIVibrator : public IVibrator {
-public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, off, (), (override));
-    MOCK_METHOD(Status, on, (int32_t timeout, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, perform,
-                (Effect e, EffectStrength s, const sp<IVibratorCallback>& cb, int32_t* ret),
-                (override));
-    MOCK_METHOD(Status, getSupportedEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, setAmplitude, (float amplitude), (override));
-    MOCK_METHOD(Status, setExternalControl, (bool enabled), (override));
-    MOCK_METHOD(Status, getCompositionDelayMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
-                (override));
-    MOCK_METHOD(Status, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret), (override));
-    MOCK_METHOD(Status, compose,
-                (const std::vector<CompositeEffect>& e, const sp<IVibratorCallback>& cb),
-                (override));
-    MOCK_METHOD(Status, composePwle,
-                (const std::vector<PrimitivePwle>& e, const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret), (override));
-    MOCK_METHOD(Status, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s), (override));
-    MOCK_METHOD(Status, alwaysOnDisable, (int32_t id), (override));
-    MOCK_METHOD(Status, getQFactor, (float * ret), (override));
-    MOCK_METHOD(Status, getResonantFrequency, (float * ret), (override));
-    MOCK_METHOD(Status, getFrequencyResolution, (float* ret), (override));
-    MOCK_METHOD(Status, getFrequencyMinimum, (float* ret), (override));
-    MOCK_METHOD(Status, getBandwidthAmplitudeMap, (std::vector<float> * ret), (override));
-    MOCK_METHOD(Status, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getPwleCompositionSizeMax, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getSupportedBraking, (std::vector<Braking> * ret), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
-};
+// -------------------------------------------------------------------------------------------------
 
 class MockIVibratorManager : public IVibratorManager {
 public:
-    MOCK_METHOD(Status, getCapabilities, (int32_t * ret), (override));
-    MOCK_METHOD(Status, getVibratorIds, (std::vector<int32_t> * ret), (override));
-    MOCK_METHOD(Status, getVibrator, (int32_t id, sp<IVibrator>* ret), (override));
-    MOCK_METHOD(Status, prepareSynced, (const std::vector<int32_t>& ids), (override));
-    MOCK_METHOD(Status, triggerSynced, (const sp<IVibratorCallback>& cb), (override));
-    MOCK_METHOD(Status, cancelSynced, (), (override));
-    MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
-    MOCK_METHOD(std::string, getInterfaceHash, (), (override));
-    MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+    MockIVibratorManager() = default;
+
+    MOCK_METHOD(ndk::ScopedAStatus, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getVibratorIds, (std::vector<int32_t> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getVibrator, (int32_t id, std::shared_ptr<IVibrator>* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, prepareSynced, (const std::vector<int32_t>& ids), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, triggerSynced, (const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, cancelSynced, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t*), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string*), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -108,9 +66,8 @@
 class VibratorManagerHalWrapperAidlTest : public Test {
 public:
     void SetUp() override {
-        mMockBinder = new StrictMock<MockBinder>();
-        mMockVibrator = new StrictMock<MockIVibrator>();
-        mMockHal = new StrictMock<MockIVibratorManager>();
+        mMockVibrator = ndk::SharedRefBase::make<StrictMock<vibrator::MockIVibrator>>();
+        mMockHal = ndk::SharedRefBase::make<StrictMock<MockIVibratorManager>>();
         mMockScheduler = std::make_shared<StrictMock<vibrator::MockCallbackScheduler>>();
         mWrapper = std::make_unique<vibrator::AidlManagerHalWrapper>(mMockScheduler, mMockHal);
         ASSERT_NE(mWrapper, nullptr);
@@ -119,9 +76,8 @@
 protected:
     std::shared_ptr<StrictMock<vibrator::MockCallbackScheduler>> mMockScheduler = nullptr;
     std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
-    sp<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
-    sp<StrictMock<MockIVibrator>> mMockVibrator = nullptr;
-    sp<StrictMock<MockBinder>> mMockBinder = nullptr;
+    std::shared_ptr<StrictMock<MockIVibratorManager>> mMockHal = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockIVibrator>> mMockVibrator = nullptr;
 };
 
 // -------------------------------------------------------------------------------------------------
@@ -129,32 +85,13 @@
 static const std::vector<int32_t> kVibratorIds = {1, 2};
 static constexpr int kVibratorId = 1;
 
-ACTION(TriggerCallback) {
-    if (arg0 != nullptr) {
-        arg0->onComplete();
-    }
-}
-
-TEST_F(VibratorManagerHalWrapperAidlTest, TestPing) {
-    EXPECT_CALL(*mMockHal.get(), onAsBinder())
-            .Times(Exactly(2))
-            .WillRepeatedly(Return(mMockBinder.get()));
-    EXPECT_CALL(*mMockBinder.get(), pingBinder())
-            .Times(Exactly(2))
-            .WillOnce(Return(android::OK))
-            .WillRepeatedly(Return(android::DEAD_OBJECT));
-
-    ASSERT_TRUE(mWrapper->ping().isOk());
-    ASSERT_TRUE(mWrapper->ping().isFailed());
-}
-
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesDoesNotCacheFailedResult) {
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                            Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getCapabilities().isUnsupported());
     ASSERT_TRUE(mWrapper->getCapabilities().isFailed());
@@ -167,7 +104,8 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetCapabilitiesCachesResult) {
     EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                            Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -187,10 +125,9 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsDoesNotCacheFailedResult) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isUnsupported());
     ASSERT_TRUE(mWrapper->getVibratorIds().isFailed());
@@ -203,7 +140,7 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorIdsCachesResult) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     std::vector<std::thread> threads;
     for (int i = 0; i < 10; i++) {
@@ -225,11 +162,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
         EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
     }
 
     auto result = mWrapper->getVibrator(kVibratorId);
@@ -241,7 +178,7 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorWithInvalidIdFails) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     ASSERT_TRUE(mWrapper->getVibrator(0).isFailed());
 }
@@ -249,20 +186,21 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestGetVibratorRecoversVibratorPointer) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(Eq(kVibratorId), _))
             .Times(Exactly(3))
             .WillOnce(DoAll(SetArgPointee<1>(nullptr),
-                            Return(Status::fromExceptionCode(
-                                    Status::Exception::EX_TRANSACTION_FAILED))))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+                            Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED))))
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockVibrator.get(), off())
             .Times(Exactly(3))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_TRANSACTION_FAILED)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_TRANSACTION_FAILED)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     // Get vibrator controller is successful even if first getVibrator.
     auto result = mWrapper->getVibrator(kVibratorId);
@@ -281,18 +219,19 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestPrepareSynced) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
             .Times(Exactly(2))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), prepareSynced(Eq(kVibratorIds)))
             .Times(Exactly(3))
-            .WillOnce(
-                    Return(Status::fromExceptionCode(Status::Exception::EX_UNSUPPORTED_OPERATION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
     ASSERT_TRUE(mWrapper->prepareSynced(kVibratorIds).isUnsupported());
@@ -305,13 +244,13 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
-                                      Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_TRIGGER_CALLBACK),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), triggerSynced(_))
                 .Times(Exactly(3))
-                .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
-                .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-                .WillRepeatedly(DoAll(TriggerCallback(), Return(Status())));
+                .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)))
+                .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+                .WillOnce(DoAll(vibrator::TriggerCallback(), Return(ndk::ScopedAStatus::ok())));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -328,11 +267,11 @@
         InSequence seq;
         EXPECT_CALL(*mMockHal.get(), getCapabilities(_))
                 .Times(Exactly(1))
-                .WillRepeatedly(
-                        DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC), Return(Status())));
+                .WillOnce(DoAll(SetArgPointee<0>(IVibratorManager::CAP_SYNC),
+                                Return(ndk::ScopedAStatus::ok())));
         EXPECT_CALL(*mMockHal.get(), triggerSynced(Eq(nullptr)))
                 .Times(Exactly(1))
-                .WillRepeatedly(Return(Status()));
+                .WillOnce(Return(ndk::ScopedAStatus::ok()));
     }
 
     std::unique_ptr<int32_t> callbackCounter = std::make_unique<int32_t>();
@@ -345,9 +284,9 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSynced) {
     EXPECT_CALL(*mMockHal.get(), cancelSynced())
             .Times(Exactly(3))
-            .WillOnce(Return(Status::fromStatusT(UNKNOWN_TRANSACTION)))
-            .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_SECURITY)))
-            .WillRepeatedly(Return(Status()));
+            .WillOnce(Return(ndk::ScopedAStatus::fromStatus(STATUS_UNKNOWN_TRANSACTION)))
+            .WillOnce(Return(ndk::ScopedAStatus::fromExceptionCode(EX_SECURITY)))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->cancelSynced().isUnsupported());
     ASSERT_TRUE(mWrapper->cancelSynced().isFailed());
@@ -357,13 +296,17 @@
 TEST_F(VibratorManagerHalWrapperAidlTest, TestCancelSyncedReloadsAllControllers) {
     EXPECT_CALL(*mMockHal.get(), getVibratorIds(_))
             .Times(Exactly(1))
-            .WillRepeatedly(DoAll(SetArgPointee<0>(kVibratorIds), Return(Status())));
+            .WillOnce(DoAll(SetArgPointee<0>(kVibratorIds), Return(ndk::ScopedAStatus::ok())));
 
     EXPECT_CALL(*mMockHal.get(), getVibrator(_, _))
             .Times(Exactly(2))
-            .WillRepeatedly(DoAll(SetArgPointee<1>(mMockVibrator), Return(Status())));
+            // ndk::ScopedAStatus::ok() cannot be copy-constructed so can't use WillRepeatedly
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())))
+            .WillOnce(DoAll(SetArgPointee<1>(mMockVibrator), Return(ndk::ScopedAStatus::ok())));
 
-    EXPECT_CALL(*mMockHal.get(), cancelSynced()).Times(Exactly(1)).WillRepeatedly(Return(Status()));
+    EXPECT_CALL(*mMockHal.get(), cancelSynced())
+            .Times(Exactly(1))
+            .WillOnce(Return(ndk::ScopedAStatus::ok()));
 
     ASSERT_TRUE(mWrapper->getVibratorIds().isOk());
     ASSERT_TRUE(mWrapper->cancelSynced().isOk());
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
index 0850ef3..7877236 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperLegacyTest.cpp
@@ -23,10 +23,12 @@
 
 #include <vibratorservice/VibratorManagerHalWrapper.h>
 
-using android::hardware::vibrator::CompositeEffect;
-using android::hardware::vibrator::CompositePrimitive;
-using android::hardware::vibrator::Effect;
-using android::hardware::vibrator::EffectStrength;
+#include "test_mocks.h"
+
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
 
 using std::chrono::milliseconds;
 
@@ -35,27 +37,16 @@
 
 // -------------------------------------------------------------------------------------------------
 
-class MockHalController : public vibrator::HalController {
-public:
-    MockHalController() = default;
-    virtual ~MockHalController() = default;
-
-    MOCK_METHOD(bool, init, (), (override));
-    MOCK_METHOD(void, tryReconnect, (), (override));
-};
-
-// -------------------------------------------------------------------------------------------------
-
 class VibratorManagerHalWrapperLegacyTest : public Test {
 public:
     void SetUp() override {
-        mMockController = std::make_shared<StrictMock<MockHalController>>();
+        mMockController = std::make_shared<StrictMock<vibrator::MockHalController>>();
         mWrapper = std::make_unique<vibrator::LegacyManagerHalWrapper>(mMockController);
         ASSERT_NE(mWrapper, nullptr);
     }
 
 protected:
-    std::shared_ptr<StrictMock<MockHalController>> mMockController = nullptr;
+    std::shared_ptr<StrictMock<vibrator::MockHalController>> mMockController = nullptr;
     std::unique_ptr<vibrator::ManagerHalWrapper> mWrapper = nullptr;
 };
 
diff --git a/services/vibratorservice/test/test_mocks.h b/services/vibratorservice/test/test_mocks.h
new file mode 100644
index 0000000..5e09084
--- /dev/null
+++ b/services/vibratorservice/test/test_mocks.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2024 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 VIBRATORSERVICE_UNITTEST_MOCKS_H_
+#define VIBRATORSERVICE_UNITTEST_MOCKS_H_
+
+#include <gmock/gmock.h>
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+#include <vibratorservice/VibratorCallbackScheduler.h>
+#include <vibratorservice/VibratorHalController.h>
+#include <vibratorservice/VibratorHalWrapper.h>
+
+namespace android {
+
+namespace vibrator {
+
+using std::chrono::milliseconds;
+
+using namespace testing;
+
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::Effect;
+using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrator;
+using aidl::android::hardware::vibrator::IVibratorCallback;
+using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::PwleV2OutputMapEntry;
+using aidl::android::hardware::vibrator::PwleV2Primitive;
+using aidl::android::hardware::vibrator::VendorEffect;
+
+// -------------------------------------------------------------------------------------------------
+
+class MockIVibrator : public IVibrator {
+public:
+    MockIVibrator() = default;
+
+    MOCK_METHOD(ndk::ScopedAStatus, getCapabilities, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, off, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, on,
+                (int32_t timeout, const std::shared_ptr<IVibratorCallback>& cb), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, perform,
+                (Effect e, EffectStrength s, const std::shared_ptr<IVibratorCallback>& cb,
+                 int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, performVendorEffect,
+                (const VendorEffect& e, const std::shared_ptr<IVibratorCallback>& cb), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedEffects, (std::vector<Effect> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setAmplitude, (float amplitude), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, setExternalControl, (bool enabled), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getCompositionDelayMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getCompositionSizeMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedPrimitives, (std::vector<CompositePrimitive> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPrimitiveDuration, (CompositePrimitive p, int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, compose,
+                (const std::vector<CompositeEffect>& e,
+                 const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, composePwle,
+                (const std::vector<PrimitivePwle>& e, const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedAlwaysOnEffects, (std::vector<Effect> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, alwaysOnEnable, (int32_t id, Effect e, EffectStrength s),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getQFactor, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getResonantFrequency, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getFrequencyResolution, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getFrequencyMinimum, (float* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getBandwidthAmplitudeMap, (std::vector<float> * ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwlePrimitiveDurationMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleCompositionSizeMax, (int32_t * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getSupportedBraking, (std::vector<Braking> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleV2FrequencyToOutputAccelerationMap,
+                (std::vector<PwleV2OutputMapEntry> * ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleV2PrimitiveDurationMaxMillis, (int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleV2PrimitiveDurationMinMillis, (int32_t* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getPwleV2CompositionSizeMax, (int32_t* ret), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, composePwleV2,
+                (const std::vector<PwleV2Primitive>& e,
+                 const std::shared_ptr<IVibratorCallback>& cb),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t*), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string*), (override));
+    MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+    MOCK_METHOD(bool, isRemote, (), (override));
+};
+
+// gmock requirement to provide a WithArg<0>(TriggerCallback()) matcher
+typedef void TriggerCallbackFunction(const std::shared_ptr<IVibratorCallback>&);
+
+class TriggerCallbackAction : public ActionInterface<TriggerCallbackFunction> {
+public:
+    explicit TriggerCallbackAction() {}
+
+    virtual Result Perform(const ArgumentTuple& args) {
+        const std::shared_ptr<IVibratorCallback>& callback = get<0>(args);
+        if (callback) {
+            callback->onComplete();
+        }
+    }
+};
+
+inline Action<TriggerCallbackFunction> TriggerCallback() {
+    return MakeAction(new TriggerCallbackAction());
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class MockCallbackScheduler : public CallbackScheduler {
+public:
+    MOCK_METHOD(void, schedule, (std::function<void()> callback, std::chrono::milliseconds delay),
+                (override));
+};
+
+ACTION(TriggerSchedulerCallback) {
+    arg0();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+class MockHalWrapper : public HalWrapper {
+public:
+    MockHalWrapper(std::shared_ptr<CallbackScheduler> scheduler) : HalWrapper(scheduler) {}
+    virtual ~MockHalWrapper() = default;
+
+    MOCK_METHOD(vibrator::HalResult<void>, ping, (), (override));
+    MOCK_METHOD(void, tryReconnect, (), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, on,
+                (milliseconds timeout, const std::function<void()>& completionCallback),
+                (override));
+    MOCK_METHOD(vibrator::HalResult<void>, off, (), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, setAmplitude, (float amplitude), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, setExternalControl, (bool enabled), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnEnable,
+                (int32_t id, Effect effect, EffectStrength strength), (override));
+    MOCK_METHOD(vibrator::HalResult<void>, alwaysOnDisable, (int32_t id), (override));
+    MOCK_METHOD(vibrator::HalResult<milliseconds>, performEffect,
+                (Effect effect, EffectStrength strength,
+                 const std::function<void()>& completionCallback),
+                (override));
+    MOCK_METHOD(vibrator::HalResult<vibrator::Capabilities>, getCapabilitiesInternal, (),
+                (override));
+
+    CallbackScheduler* getCallbackScheduler() { return mCallbackScheduler.get(); }
+};
+
+class MockHalController : public vibrator::HalController {
+public:
+    MockHalController() = default;
+    virtual ~MockHalController() = default;
+
+    MOCK_METHOD(bool, init, (), (override));
+    MOCK_METHOD(void, tryReconnect, (), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+} // namespace vibrator
+
+} // namespace android
+
+#endif // VIBRATORSERVICE_UNITTEST_MOCKS_H_
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index c08cfc6..e99965c 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -17,7 +17,7 @@
 #ifndef VIBRATORSERVICE_UNITTEST_UTIL_H_
 #define VIBRATORSERVICE_UNITTEST_UTIL_H_
 
-#include <android/hardware/vibrator/IVibrator.h>
+#include <aidl/android/hardware/vibrator/IVibrator.h>
 
 #include <vibratorservice/VibratorHalWrapper.h>
 
@@ -25,24 +25,12 @@
 
 namespace vibrator {
 
-using ::android::hardware::vibrator::ActivePwle;
-using ::android::hardware::vibrator::Braking;
-using ::android::hardware::vibrator::BrakingPwle;
-using ::android::hardware::vibrator::CompositeEffect;
-using ::android::hardware::vibrator::CompositePrimitive;
-using ::android::hardware::vibrator::PrimitivePwle;
-
-// -------------------------------------------------------------------------------------------------
-
-class MockCallbackScheduler : public vibrator::CallbackScheduler {
-public:
-    MOCK_METHOD(void, schedule, (std::function<void()> callback, std::chrono::milliseconds delay),
-                (override));
-};
-
-ACTION(TriggerSchedulerCallback) {
-    arg0();
-}
+using aidl::android::hardware::vibrator::ActivePwle;
+using aidl::android::hardware::vibrator::Braking;
+using aidl::android::hardware::vibrator::BrakingPwle;
+using aidl::android::hardware::vibrator::CompositeEffect;
+using aidl::android::hardware::vibrator::CompositePrimitive;
+using aidl::android::hardware::vibrator::PrimitivePwle;
 
 // -------------------------------------------------------------------------------------------------
 
@@ -119,4 +107,4 @@
 
 } // namespace android
 
-#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
\ No newline at end of file
+#endif // VIBRATORSERVICE_UNITTEST_UTIL_H_
diff --git a/vulkan/libvulkan/Android.bp b/vulkan/libvulkan/Android.bp
index 436e6c6..879d2d0 100644
--- a/vulkan/libvulkan/Android.bp
+++ b/vulkan/libvulkan/Android.bp
@@ -27,9 +27,18 @@
     symbol_file: "libvulkan.map.txt",
     first_version: "24",
     unversioned_until: "current",
-    export_header_libs: [
-        "ndk_vulkan_headers",
-    ],
+}
+
+aconfig_declarations {
+    name: "libvulkan_flags",
+    package: "com.android.graphics.libvulkan.flags",
+    container: "system",
+    srcs: ["libvulkan_flags.aconfig"],
+}
+
+cc_aconfig_library {
+    name: "libvulkanflags",
+    aconfig_declarations: "libvulkan_flags",
 }
 
 cc_library_shared {
@@ -113,5 +122,8 @@
         "android.hardware.graphics.common@1.0",
         "libSurfaceFlingerProp",
     ],
-    static_libs: ["libgrallocusage"],
+    static_libs: [
+        "libgrallocusage",
+        "libvulkanflags",
+    ],
 }
diff --git a/vulkan/libvulkan/api_gen.cpp b/vulkan/libvulkan/api_gen.cpp
index a9706bc..9ff0b46 100644
--- a/vulkan/libvulkan/api_gen.cpp
+++ b/vulkan/libvulkan/api_gen.cpp
@@ -25,6 +25,9 @@
 #undef VK_NO_PROTOTYPES
 #include "api.h"
 
+/*
+ * This file is autogenerated by api_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace api {
 
@@ -178,7 +181,7 @@
     INIT_PROC(false, instance, GetPhysicalDeviceExternalSemaphoreProperties);
     INIT_PROC(false, instance, GetPhysicalDeviceExternalFenceProperties);
     INIT_PROC(false, instance, EnumeratePhysicalDeviceGroups);
-    INIT_PROC_EXT(KHR_swapchain, false, instance, GetPhysicalDevicePresentRectanglesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, instance, GetPhysicalDevicePresentRectanglesKHR);
     INIT_PROC(false, instance, GetPhysicalDeviceToolProperties);
     // clang-format on
 
@@ -325,9 +328,9 @@
     INIT_PROC(false, dev, BindBufferMemory2);
     INIT_PROC(false, dev, BindImageMemory2);
     INIT_PROC(false, dev, CmdSetDeviceMask);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupPresentCapabilitiesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, GetDeviceGroupSurfacePresentModesKHR);
-    INIT_PROC_EXT(KHR_swapchain, false, dev, AcquireNextImage2KHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupPresentCapabilitiesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, GetDeviceGroupSurfacePresentModesKHR);
+    INIT_PROC_EXT(KHR_swapchain, true, dev, AcquireNextImage2KHR);
     INIT_PROC(false, dev, CmdDispatchBase);
     INIT_PROC(false, dev, CreateDescriptorUpdateTemplate);
     INIT_PROC(false, dev, DestroyDescriptorUpdateTemplate);
@@ -659,6 +662,8 @@
         "vkGetDrmDisplayEXT",
         "vkGetInstanceProcAddr",
         "vkGetPhysicalDeviceCalibrateableTimeDomainsEXT",
+        "vkGetPhysicalDeviceCalibrateableTimeDomainsKHR",
+        "vkGetPhysicalDeviceCooperativeMatrixPropertiesKHR",
         "vkGetPhysicalDeviceDisplayPlaneProperties2KHR",
         "vkGetPhysicalDeviceDisplayProperties2KHR",
         "vkGetPhysicalDeviceExternalBufferProperties",
@@ -703,6 +708,7 @@
         "vkGetPhysicalDeviceToolProperties",
         "vkGetPhysicalDeviceToolPropertiesEXT",
         "vkGetPhysicalDeviceVideoCapabilitiesKHR",
+        "vkGetPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR",
         "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
         "vkSubmitDebugUtilsMessageEXT",
     };
diff --git a/vulkan/libvulkan/api_gen.h b/vulkan/libvulkan/api_gen.h
index 4998018..b468a89 100644
--- a/vulkan/libvulkan/api_gen.h
+++ b/vulkan/libvulkan/api_gen.h
@@ -25,6 +25,9 @@
 
 #include "driver_gen.h"
 
+/*
+ * This file is autogenerated by api_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace api {
 
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index 81fd118..01436db 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -41,10 +41,12 @@
 #include <new>
 #include <vector>
 
+#include <com_android_graphics_libvulkan_flags.h>
 #include "stubhal.h"
 
 using namespace android::hardware::configstore;
 using namespace android::hardware::configstore::V1_0;
+using namespace com::android::graphics::libvulkan;
 
 extern "C" android_namespace_t* android_get_exported_namespace(const char*);
 
@@ -339,10 +341,13 @@
 
     ALOGD("Unload builtin Vulkan driver.");
 
-    // Close the opened device
-    int err = hal_.dev_->common.close(
-        const_cast<struct hw_device_t*>(&hal_.dev_->common));
-    ALOG_ASSERT(!err, "hw_device_t::close() failed.");
+    if (hal_.dev_->common.close != nullptr)
+    {
+        // Close the opened device
+        int err = hal_.dev_->common.close(
+            const_cast<struct hw_device_t*>(&hal_.dev_->common));
+        ALOG_ASSERT(!err, "hw_device_t::close() failed.");
+    }
 
     // Close the opened shared library in the hw_module_t
     android_unload_sphal_library(hal_.dev_->common.module->dso);
@@ -685,6 +690,7 @@
             case ProcHook::KHR_incremental_present:
             case ProcHook::KHR_shared_presentable_image:
             case ProcHook::KHR_swapchain:
+            case ProcHook::KHR_swapchain_mutable_format:
             case ProcHook::EXT_hdr_metadata:
             case ProcHook::EXT_swapchain_maintenance1:
             case ProcHook::ANDROID_external_memory_android_hardware_buffer:
@@ -737,6 +743,7 @@
                 break;
             case ProcHook::ANDROID_external_memory_android_hardware_buffer:
             case ProcHook::KHR_external_fence_fd:
+            case ProcHook::KHR_swapchain_mutable_format:
             case ProcHook::EXTENSION_UNKNOWN:
                 // Extensions we don't need to do anything about at this level
                 break;
@@ -964,14 +971,20 @@
 
 PFN_vkVoidFunction GetDeviceProcAddr(VkDevice device, const char* pName) {
     const ProcHook* hook = GetProcHook(pName);
+    PFN_vkVoidFunction drv_func = GetData(device).driver.GetDeviceProcAddr(device, pName);
+
     if (!hook)
-        return GetData(device).driver.GetDeviceProcAddr(device, pName);
+        return drv_func;
 
     if (hook->type != ProcHook::DEVICE) {
         ALOGE("internal vkGetDeviceProcAddr called for %s", pName);
         return nullptr;
     }
 
+    // Don't hook if we don't have a device entry function below for the core function.
+    if (!drv_func && (hook->extension >= ProcHook::EXTENSION_CORE_1_0))
+        return nullptr;
+
     return (GetData(device).hook_extensions[hook->extension]) ? hook->proc
                                                               : nullptr;
 }
@@ -1242,6 +1255,15 @@
                 VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION});
     }
 
+    VkPhysicalDeviceProperties pDeviceProperties;
+    data.driver.GetPhysicalDeviceProperties(physicalDevice, &pDeviceProperties);
+    if (flags::swapchain_mutable_format_ext() &&
+        pDeviceProperties.apiVersion >= VK_API_VERSION_1_2) {
+        loader_extensions.push_back(
+            {VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_EXTENSION_NAME,
+             VK_KHR_SWAPCHAIN_MUTABLE_FORMAT_SPEC_VERSION});
+    }
+
     // enumerate our extensions first
     if (!pLayerName && pProperties) {
         uint32_t count = std::min(
@@ -1372,6 +1394,11 @@
             android::GraphicsEnv::getInstance().setTargetStats(
                 android::GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION,
                 vulkanApiVersion);
+
+            if (pCreateInfo->pApplicationInfo->pEngineName) {
+                android::GraphicsEnv::getInstance().addVulkanEngineName(
+                    pCreateInfo->pApplicationInfo->pEngineName);
+            }
         }
 
         // Update stats for the extensions requested
diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp
index 8f09008..f741977 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -26,6 +26,9 @@
 namespace vulkan {
 namespace driver {
 
+/*
+ * This file is autogenerated by driver_generator.py. Do not edit directly.
+ */
 namespace {
 
 // clang-format off
@@ -613,6 +616,7 @@
     if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities;
     if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities;
     if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd;
+    if (strcmp(name, "VK_KHR_swapchain_mutable_format") == 0) return ProcHook::KHR_swapchain_mutable_format;
     // clang-format on
     return ProcHook::EXTENSION_UNKNOWN;
 }
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 4527214..649c0f1 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -26,6 +26,9 @@
 #include <optional>
 #include <vector>
 
+/*
+ * This file is autogenerated by driver_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace driver {
 
@@ -59,6 +62,7 @@
         KHR_external_semaphore_capabilities,
         KHR_external_fence_capabilities,
         KHR_external_fence_fd,
+        KHR_swapchain_mutable_format,
 
         EXTENSION_CORE_1_0,
         EXTENSION_CORE_1_1,
diff --git a/vulkan/libvulkan/libvulkan_flags.aconfig b/vulkan/libvulkan/libvulkan_flags.aconfig
new file mode 100644
index 0000000..891bc02
--- /dev/null
+++ b/vulkan/libvulkan/libvulkan_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.graphics.libvulkan.flags"
+container: "system"
+
+flag {
+  name: "swapchain_mutable_format_ext"
+  namespace: "core_graphics"
+  description: "Enable the VK_KHR_swapchain_mutable_format vulkan extension"
+  bug: "341978292"
+  is_fixed_read_only: true
+}
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 1314193..09b0a14 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -555,8 +555,7 @@
     return native_format;
 }
 
-DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
-                             PixelFormat pixelFormat) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace, VkFormat format) {
     switch (colorspace) {
         case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
             return DataSpace::SRGB;
@@ -575,7 +574,7 @@
         case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
             return DataSpace::SRGB;
         case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
-            if (pixelFormat == PixelFormat::RGBA_FP16) {
+            if (format == VK_FORMAT_R16G16B16A16_SFLOAT) {
                 return DataSpace::BT2020_LINEAR_EXTENDED;
             } else {
                 return DataSpace::BT2020_LINEAR;
@@ -764,21 +763,20 @@
         {VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
     };
 
+    VkFormat format = VK_FORMAT_UNDEFINED;
     if (colorspace_ext) {
         for (VkColorSpaceKHR colorSpace :
              colorSpaceSupportedByVkEXTSwapchainColorspace) {
-            if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
-                                                   VK_FORMAT_R8G8B8A8_UNORM)) !=
-                DataSpace::UNKNOWN) {
+            format = VK_FORMAT_R8G8B8A8_UNORM;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
                 all_formats.emplace_back(
-                    VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, colorSpace});
+                    VkSurfaceFormatKHR{format, colorSpace});
             }
 
-            if (GetNativeDataspace(colorSpace, GetNativePixelFormat(
-                                                   VK_FORMAT_R8G8B8A8_SRGB)) !=
-                DataSpace::UNKNOWN) {
+            format = VK_FORMAT_R8G8B8A8_SRGB;
+            if (GetNativeDataspace(colorSpace, format) != DataSpace::UNKNOWN) {
                 all_formats.emplace_back(
-                    VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_SRGB, colorSpace});
+                    VkSurfaceFormatKHR{format, colorSpace});
             }
         }
     }
@@ -787,78 +785,73 @@
     // Android users.  This includes the ANGLE team (a layered implementation of
     // OpenGL-ES).
 
+    format = VK_FORMAT_R5G6B5_UNORM_PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R5G6B5_UNORM_PACK16, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R5G6B5_UNORM_PACK16)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R5G6B5_UNORM_PACK16, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_R16G16B16A16_SFLOAT;
     desc.format = AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT;
     if (AHardwareBuffer_isSupported(&desc)) {
-        all_formats.emplace_back(VkSurfaceFormatKHR{
-            VK_FORMAT_R16G16B16A16_SFLOAT, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+        all_formats.emplace_back(
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
 
             for (
                 VkColorSpaceKHR colorSpace :
                 colorSpaceSupportedByVkEXTSwapchainColorspaceOnFP16SurfaceOnly) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(VK_FORMAT_R16G16B16A16_SFLOAT)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R16G16B16A16_SFLOAT, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_A2B10G10R10_UNORM_PACK32;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace, GetNativePixelFormat(
-                                        VK_FORMAT_A2B10G10R10_UNORM_PACK32)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_A2B10G10R10_UNORM_PACK32, colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
     }
 
+    format = VK_FORMAT_R8_UNORM;
     desc.format = AHARDWAREBUFFER_FORMAT_R8_UNORM;
     if (AHardwareBuffer_isSupported(&desc)) {
         if (colorspace_ext) {
-            all_formats.emplace_back(VkSurfaceFormatKHR{
-                VK_FORMAT_R8_UNORM, VK_COLOR_SPACE_PASS_THROUGH_EXT});
+            all_formats.emplace_back(
+                VkSurfaceFormatKHR{format, VK_COLOR_SPACE_PASS_THROUGH_EXT});
         }
     }
 
@@ -877,22 +870,18 @@
             rgba10x6_formats_ext = true;
         }
     }
+    format = VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16;
     desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
     if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
         all_formats.emplace_back(
-            VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                               VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
+            VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
         if (colorspace_ext) {
             for (VkColorSpaceKHR colorSpace :
                  colorSpaceSupportedByVkEXTSwapchainColorspace) {
-                if (GetNativeDataspace(
-                        colorSpace,
-                        GetNativePixelFormat(
-                            VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16)) !=
+                if (GetNativeDataspace(colorSpace, format) !=
                     DataSpace::UNKNOWN) {
-                    all_formats.emplace_back(VkSurfaceFormatKHR{
-                        VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
-                        colorSpace});
+                    all_formats.emplace_back(
+                        VkSurfaceFormatKHR{format, colorSpace});
                 }
             }
         }
@@ -1185,7 +1174,8 @@
                             pSurfaceFormat);
 
                     if (surfaceCompressionProps &&
-                        driver.GetPhysicalDeviceImageFormatProperties2KHR) {
+                        (driver.GetPhysicalDeviceImageFormatProperties2KHR ||
+                         driver.GetPhysicalDeviceImageFormatProperties2)) {
                         VkPhysicalDeviceImageFormatInfo2 imageFormatInfo = {};
                         imageFormatInfo.sType =
                             VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
@@ -1216,7 +1206,7 @@
                         imageFormatProps.pNext = &compressionProps;
 
                         VkResult compressionRes =
-                            driver.GetPhysicalDeviceImageFormatProperties2KHR(
+                            GetPhysicalDeviceImageFormatProperties2(
                                 physicalDevice, &imageFormatInfo,
                                 &imageFormatProps);
                         if (compressionRes == VK_SUCCESS) {
@@ -1225,8 +1215,15 @@
                             surfaceCompressionProps
                                 ->imageCompressionFixedRateFlags =
                                 compressionProps.imageCompressionFixedRateFlags;
-                        } else {
+                        } else if (compressionRes ==
+                                       VK_ERROR_OUT_OF_HOST_MEMORY ||
+                                   compressionRes ==
+                                       VK_ERROR_OUT_OF_DEVICE_MEMORY) {
                             return compressionRes;
+                        } else {
+                            // For any of the *_NOT_SUPPORTED errors we continue
+                            // onto the next format
+                            continue;
                         }
                     }
                 } break;
@@ -1475,6 +1472,12 @@
             .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
         };
 
+        // If supporting mutable format swapchain add the mutable format flag
+        if (create_info->flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) {
+            image_format_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+            image_format_info.flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT_KHR;
+        }
+
         VkAndroidHardwareBufferUsageANDROID ahb_usage;
         ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
         ahb_usage.pNext = nullptr;
@@ -1483,23 +1486,14 @@
         image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
         image_format_properties.pNext = &ahb_usage;
 
-        if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
-                pdev, &image_format_info, &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
-        }
-        else {
-            VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
-                pdev, &image_format_info,
-                &image_format_properties);
-            if (result != VK_SUCCESS) {
-                ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
-                    result);
-                return VK_ERROR_SURFACE_LOST_KHR;
-            }
+        VkResult result = GetPhysicalDeviceImageFormatProperties2(
+            pdev, &image_format_info, &image_format_properties);
+        if (result != VK_SUCCESS) {
+            ALOGE(
+                "VkGetPhysicalDeviceImageFormatProperties2 for AHB usage "
+                "failed: %d",
+                result);
+            return VK_ERROR_SURFACE_LOST_KHR;
         }
 
         // Determine if USAGE_FRONT_BUFFER is needed.
@@ -1670,8 +1664,8 @@
 
     PixelFormat native_pixel_format =
         GetNativePixelFormat(create_info->imageFormat);
-    DataSpace native_dataspace =
-        GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
+    DataSpace native_dataspace = GetNativeDataspace(
+        create_info->imageColorSpace, create_info->imageFormat);
     if (native_dataspace == DataSpace::UNKNOWN) {
         ALOGE(
             "CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "
@@ -1902,6 +1896,11 @@
         num_images = 1;
     }
 
+    VkImageFormatListCreateInfo extra_mutable_formats = {
+        .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR,
+    };
+    VkImageFormatListCreateInfo* extra_mutable_formats_ptr;
+
     // Look through the create_info pNext chain passed to createSwapchainKHR
     // for an image compression control struct.
     // if one is found AND the appropriate extensions are enabled, create a
@@ -1920,7 +1919,29 @@
                 image_compression.pNext = nullptr;
                 usage_info_pNext = &image_compression;
             } break;
-
+            case VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO: {
+                const VkImageFormatListCreateInfo* format_list =
+                    reinterpret_cast<const VkImageFormatListCreateInfo*>(
+                        create_infos);
+                if (create_info->flags &
+                    VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) {
+                    if (format_list && format_list->viewFormatCount > 0 &&
+                        format_list->pViewFormats) {
+                        extra_mutable_formats.viewFormatCount =
+                            format_list->viewFormatCount;
+                        extra_mutable_formats.pViewFormats =
+                            format_list->pViewFormats;
+                        extra_mutable_formats_ptr = &extra_mutable_formats;
+                    } else {
+                        ALOGE(
+                            "vk_swapchain_create_mutable_format_bit_khr was "
+                            "set during swapchain creation but no valid "
+                            "vkimageformatlistcreateinfo was found in the "
+                            "pnext chain");
+                        return VK_ERROR_INITIALIZATION_FAILED;
+                    }
+                }
+            } break;
             default:
                 // Ignore all other info structs
                 break;
@@ -2016,6 +2037,11 @@
         .pQueueFamilyIndices = create_info->pQueueFamilyIndices,
     };
 
+    if (create_info->flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) {
+        image_create.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+        image_create.flags |= VK_IMAGE_CREATE_EXTENDED_USAGE_BIT_KHR;
+    }
+
     // Note: don't do deferred allocation for shared present modes. There's only one buffer
     // involved so very little benefit.
     if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) &&
@@ -2025,7 +2051,7 @@
         // AcquireNextImage.
         VkImageSwapchainCreateInfoKHR image_swapchain_create = {
             .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR,
-            .pNext = nullptr,
+            .pNext = extra_mutable_formats_ptr,
             .swapchain = HandleFromSwapchain(swapchain),
         };
         image_create.pNext = &image_swapchain_create;
@@ -2077,6 +2103,11 @@
                 ANativeWindowBuffer_getHardwareBuffer(img.buffer.get());
             image_create.pNext = &image_native_buffer;
 
+            if (extra_mutable_formats_ptr) {
+                extra_mutable_formats_ptr->pNext = image_create.pNext;
+                image_create.pNext = extra_mutable_formats_ptr;
+            }
+
             ATRACE_BEGIN("CreateImage");
             result =
                 dispatch.CreateImage(device, &image_create, nullptr, &img.image);
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index d34851e..40a45af 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -24,6 +24,9 @@
 
 using namespace null_driver;
 
+/*
+ * This file is autogenerated by null_generator.py. Do not edit directly.
+ */
 namespace {
 
 struct NameProc {
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index fb3bd05..0d1e223 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -22,6 +22,9 @@
 #include <vulkan/vk_android_native_buffer.h>
 #include <vulkan/vulkan.h>
 
+/*
+ * This file is autogenerated by null_generator.py. Do not edit directly.
+ */
 namespace null_driver {
 
 PFN_vkVoidFunction GetGlobalProcAddr(const char* name);
diff --git a/vulkan/scripts/api_generator.py b/vulkan/scripts/api_generator.py
index be24172..001af20 100644
--- a/vulkan/scripts/api_generator.py
+++ b/vulkan/scripts/api_generator.py
@@ -61,6 +61,9 @@
 
 #include "driver_gen.h"
 
+/*
+ * This file is autogenerated by api_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace api {
 
@@ -283,6 +286,9 @@
 #undef VK_NO_PROTOTYPES
 #include "api.h"
 
+/*
+ * This file is autogenerated by api_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace api {
 
diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py
index 78b550c..6159599 100644
--- a/vulkan/scripts/driver_generator.py
+++ b/vulkan/scripts/driver_generator.py
@@ -49,6 +49,7 @@
     'VK_KHR_external_semaphore_capabilities',
     'VK_KHR_external_fence_capabilities',
     'VK_KHR_external_fence_fd',
+    'VK_KHR_swapchain_mutable_format',
 ]
 
 # Functions needed at vulkan::driver level.
@@ -224,6 +225,9 @@
 #include <optional>
 #include <vector>
 
+/*
+ * This file is autogenerated by driver_generator.py. Do not edit directly.
+ */
 namespace vulkan {
 namespace driver {
 
@@ -239,6 +243,8 @@
       f.write(gencom.indent(2) + gencom.base_ext_name(ext) + ',\n')
 
     f.write('\n')
+    # EXTENSION_CORE_xxx API list must be the last set of enums after the extensions.
+    # This allows to easily identify "a" core function hook
     for version in gencom.version_code_list:
       f.write(gencom.indent(2) + 'EXTENSION_CORE_' + version + ',\n')
 
@@ -501,6 +507,9 @@
 namespace vulkan {
 namespace driver {
 
+/*
+ * This file is autogenerated by driver_generator.py. Do not edit directly.
+ */
 namespace {
 
 // clang-format off\n\n""")
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index 866c1b7..6b4cbad 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -351,9 +351,50 @@
                           'external', 'vulkan-headers', 'registry', 'vk.xml')
   tree = element_tree.parse(registry)
   root = tree.getroot()
+
+  for exts in root.iter('extensions'):
+    for extension in exts.iter('extension'):
+      if 'vulkan' not in extension.get('supported').split(','):
+        # ANDROID_native_buffer is a weird special case -- it's declared in vk.xml as
+        # disabled but we _do_ want to generate plumbing for it in the Android loader.
+        if extension.get('name') != 'VK_ANDROID_native_buffer':
+          print('skip extension disabled or not for vulkan: ' + extension.get('name'))
+          continue
+
+      apiversion = 'VK_VERSION_1_0'
+      if extension.tag == 'extension':
+        extname = extension.get('name')
+        if (extension.get('type') == 'instance' and
+            extension.get('promotedto') is not None):
+          promoted_inst_ext_dict[extname] = \
+              version_2_api_version(extension.get('promotedto'))
+        for req in extension.iter('require'):
+          if req.get('feature') is not None:
+            apiversion = req.get('feature')
+          for commands in req:
+            if commands.tag == 'command':
+              cmd_name = commands.get('name')
+              if cmd_name not in extension_dict:
+                extension_dict[cmd_name] = extname
+                version_dict[cmd_name] = apiversion
+
+  for feature in root.iter('feature'):
+    if 'vulkan' not in feature.get('api').split(','):
+      continue
+
+    apiversion = feature.get('name')
+    for req in feature.iter('require'):
+      for command in req:
+        if command.tag == 'command':
+          cmd_name = command.get('name')
+          version_dict[cmd_name] = apiversion
+
   for commands in root.iter('commands'):
     for command in commands:
       if command.tag == 'command':
+        if command.get('api') == 'vulkansc':
+          continue
+
         parameter_list = []
         protoset = False
         cmd_name = ''
@@ -361,12 +402,18 @@
         if command.get('alias') is not None:
           alias = command.get('alias')
           cmd_name = command.get('name')
-          alias_dict[cmd_name] = alias
-          command_list.append(cmd_name)
-          param_dict[cmd_name] = param_dict[alias].copy()
-          return_type_dict[cmd_name] = return_type_dict[alias]
+          # At this stage all valid commands have been added to the version
+          # dict so we can use it to filter valid commands
+          if cmd_name in version_dict:
+            alias_dict[cmd_name] = alias
+            command_list.append(cmd_name)
+            param_dict[cmd_name] = param_dict[alias].copy()
+            return_type_dict[cmd_name] = return_type_dict[alias]
         for params in command:
           if params.tag == 'param':
+            if params.get('api') == 'vulkansc':
+              # skip SC-only param variant
+              continue
             param_type = ''
             if params.text is not None and params.text.strip():
               param_type = params.text.strip() + ' '
@@ -387,39 +434,13 @@
                 cmd_type = c.text
               if c.tag == 'name':
                 cmd_name = c.text
-                protoset = True
-                command_list.append(cmd_name)
-                return_type_dict[cmd_name] = cmd_type
+                if cmd_name in version_dict:
+                  protoset = True
+                  command_list.append(cmd_name)
+                  return_type_dict[cmd_name] = cmd_type
         if protoset:
           param_dict[cmd_name] = parameter_list.copy()
 
-  for exts in root.iter('extensions'):
-    for extension in exts:
-      apiversion = 'VK_VERSION_1_0'
-      if extension.tag == 'extension':
-        extname = extension.get('name')
-        if (extension.get('type') == 'instance' and
-            extension.get('promotedto') is not None):
-          promoted_inst_ext_dict[extname] = \
-              version_2_api_version(extension.get('promotedto'))
-        for req in extension:
-          if req.get('feature') is not None:
-            apiversion = req.get('feature')
-          for commands in req:
-            if commands.tag == 'command':
-              cmd_name = commands.get('name')
-              if cmd_name not in extension_dict:
-                extension_dict[cmd_name] = extname
-                version_dict[cmd_name] = apiversion
-
-  for feature in root.iter('feature'):
-    apiversion = feature.get('name')
-    for req in feature:
-      for command in req:
-        if command.tag == 'command':
-          cmd_name = command.get('name')
-          if cmd_name in command_list:
-            version_dict[cmd_name] = apiversion
 
   version_code_set = set()
   for version in version_dict.values():
diff --git a/vulkan/scripts/null_generator.py b/vulkan/scripts/null_generator.py
index e9faef6..5c5bea3 100644
--- a/vulkan/scripts/null_generator.py
+++ b/vulkan/scripts/null_generator.py
@@ -55,6 +55,9 @@
 #include <vulkan/vk_android_native_buffer.h>
 #include <vulkan/vulkan.h>
 
+/*
+ * This file is autogenerated by null_generator.py. Do not edit directly.
+ */
 namespace null_driver {
 
 PFN_vkVoidFunction GetGlobalProcAddr(const char* name);
@@ -89,12 +92,17 @@
     f.write(gencom.copyright_and_warning(2015))
 
     f.write("""\
+#include <android/hardware_buffer.h>
+
 #include <algorithm>
 
 #include "null_driver_gen.h"
 
 using namespace null_driver;
 
+/*
+ * This file is autogenerated by null_generator.py. Do not edit directly.
+ */
 namespace {
 
 struct NameProc {
diff --git a/vulkan/vkjson/vkjson.cc b/vulkan/vkjson/vkjson.cc
index 0284192..bfb7bd6 100644
--- a/vulkan/vkjson/vkjson.cc
+++ b/vulkan/vkjson/vkjson.cc
@@ -1169,7 +1169,7 @@
   return array;
 }
 
-template <typename T, unsigned int N>
+template <typename T, size_t N>
 inline Json::Value ToJsonValue(const T (&value)[N]) {
   return ArrayToJsonValue(N, value);
 }
@@ -1293,7 +1293,7 @@
   return true;
 }
 
-template <typename T, unsigned int N>
+template <typename T, size_t N>
 inline bool AsValue(Json::Value* json_value, T (*value)[N]) {
   return AsArray(json_value, N, *value);
 }